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"
|
|
|
|
#include "preferences.h"
|
|
|
|
|
|
|
|
#include <set>
|
|
|
|
|
|
|
|
|
|
|
|
namespace Ms {
|
|
|
|
|
|
|
|
extern Preferences preferences;
|
|
|
|
|
|
|
|
namespace MidiClef {
|
|
|
|
|
|
|
|
|
|
|
|
int midPitch()
|
|
|
|
{
|
|
|
|
static const int clefMidPitch = 60;
|
|
|
|
return clefMidPitch;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isTied(const Segment *seg, int strack, int voice,
|
|
|
|
Ms::Tie*(Note::*tieFunc)() const)
|
|
|
|
{
|
|
|
|
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) {
|
|
|
|
if ((note->*tieFunc)())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isTiedFor(const Segment *seg, int strack, int voice)
|
|
|
|
{
|
|
|
|
return isTied(seg, strack, voice, &Note::tieFor);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isTiedBack(const Segment *seg, int strack, int voice)
|
|
|
|
{
|
|
|
|
return isTied(seg, strack, voice, &Note::tieBack);
|
|
|
|
}
|
|
|
|
|
|
|
|
class AveragePitch
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
AveragePitch() : sumPitch_(0), count_(0) {}
|
|
|
|
AveragePitch(int sumPitch, int count) : sumPitch_(sumPitch), count_(count) {}
|
|
|
|
|
|
|
|
int pitch() const { return sumPitch_ / count_; }
|
|
|
|
int sumPitch() const { return sumPitch_; }
|
|
|
|
int count() const { return count_; }
|
|
|
|
void addPitch(int pitch)
|
|
|
|
{
|
|
|
|
sumPitch_ += pitch;
|
|
|
|
++count_;
|
|
|
|
}
|
|
|
|
void reset()
|
|
|
|
{
|
|
|
|
sumPitch_ = 0;
|
|
|
|
count_ = 0;
|
|
|
|
}
|
|
|
|
AveragePitch& operator+=(const AveragePitch &other)
|
|
|
|
{
|
|
|
|
sumPitch_ += other.sumPitch();
|
|
|
|
count_ += other.count();
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
int sumPitch_;
|
|
|
|
int count_;
|
|
|
|
};
|
|
|
|
|
|
|
|
class TieStateMachine
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
enum class State
|
|
|
|
{
|
|
|
|
UNTIED, TIED_FOR, TIED_BOTH, TIED_BACK
|
|
|
|
};
|
|
|
|
|
|
|
|
void addSeg(const Segment *seg, int strack)
|
|
|
|
{
|
|
|
|
bool isChord = false;
|
|
|
|
for (int voice = 0; voice < VOICES; ++voice) {
|
|
|
|
ChordRest *cr = static_cast<ChordRest *>(seg->element(strack + voice));
|
|
|
|
if (!cr || cr->type() != Element::CHORD)
|
|
|
|
continue;
|
|
|
|
if (!isChord)
|
|
|
|
isChord = true;
|
|
|
|
|
|
|
|
bool tiedFor = isTiedFor(seg, strack, voice);
|
|
|
|
bool tiedBack = isTiedBack(seg, strack, voice);
|
|
|
|
|
|
|
|
if (tiedFor && !tiedBack)
|
|
|
|
tiedVoices.insert(voice);
|
|
|
|
else if (!tiedFor && tiedBack)
|
|
|
|
tiedVoices.erase(voice);
|
|
|
|
}
|
|
|
|
if (!isChord)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (tiedVoices.empty() && (state_ == State::TIED_FOR
|
|
|
|
|| state_ == State::TIED_BOTH)) {
|
|
|
|
state_ = State::TIED_BACK;
|
|
|
|
}
|
|
|
|
else if (tiedVoices.empty() && state_ == State::TIED_BACK) {
|
|
|
|
state_ = State::UNTIED;
|
|
|
|
}
|
|
|
|
else if (!tiedVoices.empty() && (state_ == State::TIED_BACK
|
|
|
|
|| state_ == State::UNTIED)) {
|
|
|
|
state_ = State::TIED_FOR;
|
|
|
|
}
|
|
|
|
else if (!tiedVoices.empty() && state_ == State::TIED_FOR) {
|
|
|
|
state_ = State::TIED_BOTH;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
State state() const { return state_; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::set<int> tiedVoices;
|
|
|
|
State state_ = State::UNTIED;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ClefType clefTypeFromAveragePitch(int averagePitch)
|
|
|
|
{
|
2013-09-05 16:37:49 +02:00
|
|
|
return averagePitch < midPitch() ? 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)
|
|
|
|
{
|
|
|
|
AveragePitch avgPitch;
|
|
|
|
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)
|
|
|
|
avgPitch.addPitch(note->pitch());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return avgPitch;
|
|
|
|
}
|
|
|
|
|
2013-08-23 23:16:10 +02:00
|
|
|
Segment* enlargeSegToPrev(Segment *s, int strack, int counterLimit, int lPitch, int hPitch)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
auto newSeg = s;
|
|
|
|
for (Segment *segPrev = s->prev1(Segment::SegChordRest); segPrev;
|
|
|
|
segPrev = segPrev->prev1(Segment::SegChordRest)) {
|
|
|
|
auto pitch = findAverageSegPitch(segPrev, strack);
|
|
|
|
if (pitch.count() > 0) {
|
|
|
|
if (pitch.pitch() >= lPitch && pitch.pitch() < hPitch)
|
|
|
|
newSeg = segPrev;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else { // it's a rest - should be at the end
|
|
|
|
s = newSeg;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
++count;
|
|
|
|
if (count == counterLimit)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2013-08-24 20:32:48 +02:00
|
|
|
void createClefs(Staff *staff, int indexOfOperation, bool isDrumTrack)
|
2013-08-23 20:53:42 +02:00
|
|
|
{
|
2013-09-05 16:37:49 +02:00
|
|
|
ClefType currentClef = staff->clefTypeList(0)._concertClef;
|
2013-08-23 20:53:42 +02:00
|
|
|
createClef(currentClef, staff, 0);
|
|
|
|
|
|
|
|
const auto trackOpers = preferences.midiImportOperations.trackOperations(indexOfOperation);
|
2013-08-24 20:32:48 +02:00
|
|
|
if (!trackOpers.changeClef || isDrumTrack)
|
2013-08-23 20:53:42 +02:00
|
|
|
return;
|
|
|
|
|
2013-08-23 23:16:10 +02:00
|
|
|
const int highPitch = 65; // all notes equal or upper - in treble clef
|
2013-08-23 20:53:42 +02:00
|
|
|
const int midPitch = 60;
|
|
|
|
const int lowPitch = 55; // all notes lower - in bass clef
|
|
|
|
const int counterLimit = 3;
|
|
|
|
int counter = 0;
|
|
|
|
Segment *prevSeg = nullptr;
|
|
|
|
Segment *tiedSeg = nullptr;
|
|
|
|
const int strack = staff->idx() * VOICES;
|
|
|
|
AveragePitch avgGroupPitch;
|
|
|
|
TieStateMachine tieTracker;
|
|
|
|
|
|
|
|
for (Segment *seg = staff->score()->firstSegment(Segment::SegChordRest); seg;
|
|
|
|
seg = seg->next1(Segment::SegChordRest)) {
|
|
|
|
const auto avgPitch = findAverageSegPitch(seg, strack);
|
|
|
|
if (avgPitch.count() == 0) // no chords
|
|
|
|
continue;
|
|
|
|
tieTracker.addSeg(seg, strack);
|
2014-03-01 14:51:29 +01:00
|
|
|
const auto tieState = tieTracker.state();
|
2013-08-23 20:53:42 +02:00
|
|
|
|
|
|
|
if (tieState != TieStateMachine::State::UNTIED)
|
|
|
|
avgGroupPitch += avgPitch;
|
|
|
|
if (tieState == TieStateMachine::State::TIED_FOR)
|
|
|
|
tiedSeg = seg;
|
|
|
|
else if (tieState == TieStateMachine::State::TIED_BACK) {
|
|
|
|
ClefType clef = clefTypeFromAveragePitch(avgGroupPitch.pitch());
|
|
|
|
if (clef != currentClef) {
|
|
|
|
currentClef = clef;
|
|
|
|
if (tiedSeg)
|
|
|
|
createSmallClef(currentClef, tiedSeg, staff);
|
|
|
|
else {
|
|
|
|
qDebug("createClefs: empty tied segment, tick = %d, that should not occur",
|
|
|
|
seg->tick());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
avgGroupPitch.reset();
|
|
|
|
tiedSeg = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int oldCounter = counter;
|
|
|
|
if (tieState != TieStateMachine::State::TIED_BOTH
|
|
|
|
&& tieState != TieStateMachine::State::TIED_BACK) {
|
|
|
|
|
2013-08-23 23:16:10 +02:00
|
|
|
if (avgPitch.pitch() < lowPitch) {
|
2013-09-05 16:37:49 +02:00
|
|
|
if (currentClef == ClefType::G) {
|
2013-08-23 23:16:10 +02:00
|
|
|
Segment *s = (counter > 0) ? prevSeg : seg;
|
2013-09-05 16:37:49 +02:00
|
|
|
currentClef = ClefType::F;
|
2013-08-23 23:16:10 +02:00
|
|
|
s = enlargeSegToPrev(s, strack, counterLimit, midPitch, highPitch);
|
|
|
|
createSmallClef(currentClef, s, staff);
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
|
|
|
}
|
2013-08-23 23:16:10 +02:00
|
|
|
else if (avgPitch.pitch() >= lowPitch && avgPitch.pitch() < midPitch) {
|
2013-09-05 16:37:49 +02:00
|
|
|
if (currentClef == ClefType::G) {
|
2013-08-23 23:16:10 +02:00
|
|
|
if (counter < counterLimit) {
|
|
|
|
if (counter == 0)
|
|
|
|
prevSeg = seg;
|
|
|
|
++counter;
|
|
|
|
}
|
|
|
|
else {
|
2013-09-05 16:37:49 +02:00
|
|
|
currentClef = ClefType::F;
|
2013-08-23 23:16:10 +02:00
|
|
|
auto s = enlargeSegToPrev(prevSeg, strack, counterLimit, midPitch, highPitch);
|
|
|
|
createSmallClef(currentClef, s, staff);
|
|
|
|
}
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
2013-08-23 23:16:10 +02:00
|
|
|
}
|
|
|
|
else if (avgPitch.pitch() >= midPitch && avgPitch.pitch() < highPitch) {
|
2013-09-05 16:37:49 +02:00
|
|
|
if (currentClef == ClefType::F) {
|
2013-08-23 23:16:10 +02:00
|
|
|
if (counter < counterLimit){
|
|
|
|
if (counter == 0)
|
|
|
|
prevSeg = seg;
|
|
|
|
++counter;
|
|
|
|
}
|
|
|
|
else {
|
2013-09-05 16:37:49 +02:00
|
|
|
currentClef = ClefType::G;
|
2013-08-23 23:16:10 +02:00
|
|
|
auto s = enlargeSegToPrev(prevSeg, strack, counterLimit, lowPitch, midPitch);
|
|
|
|
createSmallClef(currentClef, s, staff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (avgPitch.pitch() >= highPitch) {
|
2013-09-05 16:37:49 +02:00
|
|
|
if (currentClef == ClefType::F) {
|
2013-08-23 23:16:10 +02:00
|
|
|
Segment *s = (counter > 0) ? prevSeg : seg;
|
2013-09-05 16:37:49 +02:00
|
|
|
currentClef = ClefType::G;
|
2013-08-23 23:16:10 +02:00
|
|
|
s = enlargeSegToPrev(s, strack, counterLimit, lowPitch, midPitch);
|
|
|
|
createSmallClef(currentClef, s, staff);
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (counter > 0 && counter == oldCounter) {
|
|
|
|
counter = 0;
|
|
|
|
prevSeg = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace MidiClef
|
|
|
|
} // namespace Ms
|
|
|
|
|