Redesign MIDI import operations architecture

This commit is contained in:
Andrey M. Tokarev 2014-06-21 22:50:33 +04:00
parent b0d760e0bd
commit 3de0b15547
48 changed files with 1837 additions and 3150 deletions

View file

@ -150,14 +150,13 @@ QT4_WRAP_CPP (mocs
inspector/inspectorJump.h inspector/inspectorMarker.h inspector/inspectorGlissando.h
inspector/inspectorNote.h inspector/inspectorAmbitus.h
paletteBoxButton.h pathlistdialog.h exampleview.h noteGroups.h inspector/inspectorTextLine.h
importmidi_panel.h importmidi_operations.h
importmidi_opmodel.h importmidi_trmodel.h importmidi_opdelegate.h
importmidi_panel.h importmidi_operations.h importmidi_model.h importmidi_delegate.h
importmidi_operation.h importmidi_quant.h importmidi_chord.h
importmidi_inner.h importmidi_data.h importmidi_swing.h
importmidi_inner.h importmidi_swing.h
importmidi_tuplet_detect.h importmidi_tuplet_filter.h importmidi_tuplet_tonotes.h
importmidi_tuplet_voice.h importmidi_beat.h importmidi_simplify.h
debugger/debugger.h importmidi_fraction.h importmidi_drum.h
importmidi_lrhand.h importmidi_lyrics.h importmidi_trview.h importmidi_tie.h importmidi_voice.h
debugger/debugger.h importmidi_fraction.h importmidi_drum.h importmidi_view.h
importmidi_lrhand.h importmidi_lyrics.h importmidi_tie.h importmidi_voice.h
resourceManager.h downloadUtils.h
${OMR_MOCS}
${SCRIPT_MOCS}
@ -269,13 +268,13 @@ add_executable ( ${ExecutableName}
paletteBoxButton.cpp driver.cpp exportmidi.cpp noteGroups.cpp
pathlistdialog.cpp exampleview.cpp inspector/inspectorTextLine.cpp
importmidi_panel.cpp importmidi_operations.cpp miconengine.cpp
importmidi_opmodel.cpp importmidi_trmodel.cpp importmidi_opdelegate.cpp
importmidi_model.cpp importmidi_delegate.cpp
importmidi_meter.cpp importmidi_quant.cpp importmidi_tuplet.cpp importmidi_chord.cpp
importmidi_data.cpp importmidi_swing.cpp importmidi_fraction.cpp importmidi_drum.cpp
importmidi_clef.cpp importmidi_lrhand.cpp importmidi_lyrics.cpp importmidi_trview.cpp
importmidi_swing.cpp importmidi_fraction.cpp importmidi_drum.cpp
importmidi_clef.cpp importmidi_lrhand.cpp importmidi_lyrics.cpp
importmidi_inner.cpp importmidi_tie.cpp importmidi_tuplet_voice.cpp importmidi_beat.cpp
importmidi_tuplet_detect.cpp importmidi_tuplet_filter.cpp importmidi_tuplet_tonotes.cpp
importmidi_simplify.cpp importmidi_voice.cpp
importmidi_simplify.cpp importmidi_voice.cpp importmidi_view.cpp
resourceManager.cpp downloadUtils.cpp
textcursor.cpp
continuouspanel.cpp

View file

@ -313,7 +313,7 @@ Score* MuseScore::readScore(const QString& name)
return 0;
Score* score = new Score(MScore::baseStyle()); // start with built-in style
setMidiPrefOperations(name);
setReopenInProgress(name);
Score::FileError rv = Ms::readScore(score, name, false);
if (rv == Score::FileError::FILE_TOO_OLD || rv == Score::FileError::FILE_TOO_NEW) {
if (readScoreError(name, rv, true))

View file

@ -58,6 +58,7 @@
#include "importmidi_beat.h"
#include "importmidi_simplify.h"
#include "importmidi_voice.h"
#include "importmidi_operations.h"
#include <set>
@ -70,11 +71,8 @@ extern void updateNoteLines(Segment*, int track);
void cleanUpMidiEvents(std::multimap<int, MTrack> &tracks)
{
auto &opers = preferences.midiImportOperations;
for (auto &track: tracks) {
MTrack &mtrack = track.second;
opers.setCurrentTrack(mtrack.indexOfOperation);
for (auto chordIt = mtrack.chords.begin(); chordIt != mtrack.chords.end(); ) {
MidiChord &ch = chordIt->second;
@ -155,10 +153,18 @@ void quantizeAllTracks(std::multimap<int, MTrack> &tracks,
MTrack &mtrack = track.second;
// pass current track index through MidiImportOperations
// for further usage
opers.setCurrentTrack(mtrack.indexOfOperation);
opers.adaptForPercussion(mtrack.indexOfOperation, mtrack.mtrack->drumTrack());
MidiOperations::CurrentTrackSetter setCurrentTrack{opers, mtrack.indexOfOperation};
if (opers.data()->isNewlyOpened) {
opers.data()->trackOpers.isDrumTrack.setValue(
opers.currentTrack(), mtrack.mtrack->drumTrack());
if (mtrack.mtrack->drumTrack()) {
opers.data()->trackOpers.maxVoiceCount.setValue(
opers.currentTrack(), MidiOperations::VoiceCount::V_1);
}
}
const auto basicQuant = Quantize::userQuantNoteToFraction(
opers.currentTrackOperations().quantize.value);
opers.data()->trackOpers.quantValue.defaultValue());
MidiTuplet::findAllTuplets(mtrack.tuplets, mtrack.chords, sigmap, lastTick, basicQuant);
@ -270,7 +276,6 @@ MTrack::toDurationList(const Measure *measure,
const ReducedFraction &len,
Meter::DurationType durationType)
{
const bool useDots = preferences.midiImportOperations.currentTrackOperations().useDots;
// find tuplets over which duration goes
auto barTick = ReducedFraction::fromTicks(measure->tick());
auto tupletsData = MidiTuplet::findTupletsInBarForDuration(
@ -287,6 +292,9 @@ MTrack::toDurationList(const Measure *measure,
const ReducedFraction startTickInBar = startTick - barTick;
const ReducedFraction endTickInBar = startTickInBar + len;
const auto &opers = preferences.midiImportOperations;
const bool useDots = opers.data()->trackOpers.useDots.value(indexOfOperation);
return Meter::toDurationList(startTickInBar, endTickInBar,
ReducedFraction(measure->timesig()), tupletsData,
durationType, useDots);
@ -439,7 +447,9 @@ void MTrack::processPendingNotes(QList<MidiChord> &midiChords,
const int track = staff->idx() * VOICES + voice;
Drumset* drumset = staff->part()->instr()->drumset();
const bool useDrumset = staff->part()->instr()->useDrumset();
const auto opers = preferences.midiImportOperations.currentTrackOperations();
const auto& opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
// all midiChords here should have the same onTime value
// and all notes in each midiChord should have the same duration
@ -465,7 +475,7 @@ void MTrack::processPendingNotes(QList<MidiChord> &midiChords,
chord->setDurationType(d);
chord->setDuration(d.fraction());
if (opers.showStaccato
if (opers.showStaccato.value(currentTrack)
&& startChordTick == startChordTickFrac // first chord in tied chord sequence
&& midiChords.begin()->isStaccato()) {
Articulation* a = new Articulation(chord->score());
@ -559,7 +569,8 @@ void MTrack::convertTrack(const ReducedFraction &lastTick)
MidiTuplet::createTuplets(staff, tuplets);
createKeys(key);
const auto swingType = preferences.midiImportOperations.trackOperations(indexOfOperation).swing;
const auto& opers = preferences.midiImportOperations.data()->trackOpers;
const auto swingType = opers.swing.value(indexOfOperation);
Swing::detectSwing(staff, swingType);
Q_ASSERT_X(MidiTie::areTiesConsistent(staff), "MTrack::convertTrack", "Ties are inconsistent");
@ -645,12 +656,12 @@ std::multimap<int, MTrack> createMTrackList(ReducedFraction &lastTick,
}
if (events != 0) {
++trackIndex;
if (preferences.midiImportOperations.count()) {
auto trackOperations
= preferences.midiImportOperations.trackOperations(trackIndex);
if (trackOperations.doImport) {
const auto *data = preferences.midiImportOperations.data();
if (!data->isNewlyOpened) {
if (data->trackOpers.doImport.value(trackIndex)) {
track.indexOfOperation = trackIndex;
tracks.insert({trackOperations.reorderedIndex, track});
tracks.insert({data->trackOpers.trackIndexAfterShuffle.value(trackIndex),
track});
}
}
else { // if it is an initial track-list query from MIDI import panel
@ -733,7 +744,8 @@ void createMeasures(ReducedFraction &lastTick, Score *score)
if (beat > 0 || tick > 0)
++bars; // convert bar index to number of bars
const bool pickupMeasure = preferences.midiImportOperations.currentTrackOperations().pickupMeasure;
const auto& opers = preferences.midiImportOperations;
const bool pickupMeasure = opers.data()->trackOpers.searchPickupMeasure;
for (int i = 0; i < bars; ++i) {
Measure* measure = new Measure(score);
@ -811,7 +823,9 @@ void createTimeSignatures(Score *score)
continue;
Fraction newTimeSig = se.timesig();
const bool pickupMeasure = preferences.midiImportOperations.currentTrackOperations().pickupMeasure;
const auto& opers = preferences.midiImportOperations;
const bool pickupMeasure = opers.data()->trackOpers.searchPickupMeasure;
if (pickupMeasure && is == score->sigmap()->begin()) {
auto next = std::next(is);
if (next != score->sigmap()->end()) {
@ -868,46 +882,13 @@ void createNotes(const ReducedFraction &lastTick, QList<MTrack> &tracks, MidiTyp
setTrackInfo(midiType, mt);
// pass current track index to the convertTrack function
// through MidiImportOperations
preferences.midiImportOperations.setCurrentTrack(mt.indexOfOperation);
auto &opers = preferences.midiImportOperations;
MidiOperations::CurrentTrackSetter setCurrentTrack(opers, mt.indexOfOperation);
mt.convertTrack(lastTick);
processMeta(mt, true);
}
}
QList<TrackMeta> getTracksMeta(const QList<MTrack> &tracks,
const MidiFile *mf)
{
QList<TrackMeta> tracksMeta;
for (int i = 0; i < tracks.size(); ++i) {
const MTrack &mt = tracks[i];
std::string trackName;
for (const auto &ie: mt.mtrack->events()) {
const MidiEvent &e = ie.second;
if ((e.type() == ME_META) && (e.metaType() == META_TRACK_NAME)) {
trackName = (const char*)e.edata();
break;
}
}
QString instrName;
if (i % 2 && isSameChannel(tracks[i - 1], tracks[i])){
TrackMeta lastMeta = tracksMeta.back();
instrName = lastMeta.instrumentName;
}
else {
MidiType midiType = mf->midiType();
if (midiType == MidiType::UNKNOWN)
midiType = MidiType::GM;
instrName = instrumentName(midiType, mt.program,
mt.mtrack->drumTrack());
}
tracksMeta.push_back({trackName,
instrName,
mt.mtrack->drumTrack(),
mt.initLyricTrackIndex
});
}
return tracksMeta;
}
void setLeftRightHandSplit(const std::multimap<int, MTrack> &tracks)
{
@ -917,7 +898,8 @@ void setLeftRightHandSplit(const std::multimap<int, MTrack> &tracks)
bool needToSplit = false;
if (LRHand::needToSplit(mtrack.chords, mtrack.program))
needToSplit = true;
preferences.midiImportOperations.setNeedToSplit(trackIndex, needToSplit);
preferences.midiImportOperations.data()->trackOpers.doStaffSplit.setValue(
trackIndex, needToSplit);
}
}
@ -930,18 +912,21 @@ void convertMidi(Score *score, const MidiFile *mf)
cleanUpMidiEvents(tracks);
auto &opers = preferences.midiImportOperations;
if (opers.count() == 0) // for newly opened MIDI file
if (opers.data()->isNewlyOpened) { // for newly opened MIDI file
opers.data()->trackCount = tracks.size();
MidiLyrics::extractLyricsToMidiData(mf);
}
// for newly opened MIDI file - detect if it is a human performance
// if so - detect beats and set initial time signature
if (opers.count() == 0 && opers.defaultOperations().canRedefineDefaultsLater)
if (opers.data()->isNewlyOpened && opers.data()->canRedefineDefaultsLater)
Quantize::setIfHumanPerformance(tracks, sigmap);
else // user value
MidiBeat::setTimeSignature(sigmap);
Q_ASSERT_X(preferences.midiImportOperations.isHumanPerformance()
? opers.timeSignature() != ReducedFraction(0, 1) : true,
Q_ASSERT_X((opers.data()->trackOpers.isHumanPerformance)
? Meter::userTimeSigToFraction(opers.data()->trackOpers.timeSigNumerator,
opers.data()->trackOpers.timeSigDenominator)
!= ReducedFraction(0, 1) : true,
"convertMidi", "Null time signature for human-performed MIDI file");
MChord::collectChords(tracks);
@ -949,7 +934,7 @@ void convertMidi(Score *score, const MidiFile *mf)
MChord::mergeChordsWithEqualOnTimeAndVoice(tracks);
// for newly opened MIDI file
if (opers.count() == 0 && opers.defaultOperations().canRedefineDefaultsLater)
if (opers.data()->isNewlyOpened && opers.data()->canRedefineDefaultsLater)
setLeftRightHandSplit(tracks);
MChord::removeOverlappingNotes(tracks);
@ -985,10 +970,7 @@ void convertMidi(Score *score, const MidiFile *mf)
score->connectTies();
MidiLyrics::setLyricsToScore(trackList);
if (opers.count() == 0) {
// clear defaults - they can be set during opening of this new MIDI file
opers.clear();
}
opers.data()->isNewlyOpened = false;
}
void loadMidiData(MidiFile &mf)
@ -1000,51 +982,23 @@ void loadMidiData(MidiFile &mf)
mf.setMidiType(mt);
}
// for new MIDI file called AFTER importMidi
QList<TrackMeta> extractMidiTracksMeta(const QString &fileName)
{
if (fileName.isEmpty())
return QList<TrackMeta>();
auto &midiData = preferences.midiImportOperations.midiData();
if (!midiData.midiFile(fileName)) {
QFile fp(fileName);
if (!fp.open(QIODevice::ReadOnly))
return QList<TrackMeta>();
MidiFile mf;
try {
mf.read(&fp);
}
catch (...) {
fp.close();
return QList<TrackMeta>();
}
fp.close();
loadMidiData(mf);
midiData.setMidiFile(fileName, mf);
}
Score mockScore;
ReducedFraction lastTick;
const MidiFile *mf = midiData.midiFile(fileName);
const auto tracks = createMTrackList(lastTick, mockScore.sigmap(), mf);
QList<MTrack> trackList = prepareTrackList(tracks);
MidiLyrics::assignLyricsToTracks(trackList);
return getTracksMeta(trackList, mf);
}
// for new MIDI file called BEFORE extractMidiTracksMeta
Score::FileError importMidi(Score *score, const QString &name)
{
if (name.isEmpty())
return Score::FileError::FILE_NOT_FOUND;
auto &midiData = preferences.midiImportOperations.midiData();
if (!midiData.midiFile(name)) {
auto &opers = preferences.midiImportOperations;
if (!opers.hasFile(name)) {
opers.addNewFile(name);
opers.data()->isNewlyOpened = true;
}
else {
opers.setCurrentMidiFile(name);
}
if (opers.data()->isNewlyOpened) {
QFile fp(name);
if (!fp.open(QIODevice::ReadOnly)) {
qDebug("importMidi: file open error <%s>", qPrintable(name));
@ -1068,10 +1022,10 @@ Score::FileError importMidi(Score *score, const QString &name)
fp.close();
loadMidiData(mf);
midiData.setMidiFile(name, mf);
opers.setMidiFileData(name, mf);
}
convertMidi(score, midiData.midiFile(name));
convertMidi(score, opers.midiFile(name));
return Score::FileError::FILE_NO_ERROR;
}

View file

@ -5,6 +5,7 @@
#include "importmidi_inner.h"
#include "importmidi_quant.h"
#include "importmidi_meter.h"
#include "importmidi_operations.h"
#include "thirdparty/beatroot/BeatTracker.h"
#include "preferences.h"
#include "libmscore/mscore.h"
@ -160,13 +161,13 @@ void addLastBeats(
}
}
HumanBeatData prepareHumanBeatData(
MidiOperations::HumanBeatData prepareHumanBeatData(
const std::vector<double> &beatTimes,
const std::multimap<ReducedFraction, MidiChord> &chords,
double ticksPerSec,
int beatsInBar)
{
HumanBeatData beatData;
MidiOperations::HumanBeatData beatData;
if (chords.empty())
return beatData;
@ -284,7 +285,7 @@ void findBeatLocations(
salienceFuncs = {findChordSalience1, findChordSalience2};
// <match rank, beat data, comparator>
std::map<double, HumanBeatData, std::greater<double>> beatResults;
std::map<double, MidiOperations::HumanBeatData, std::greater<double>> beatResults;
for (const auto &func: salienceFuncs) {
const auto events = prepareChordEvents(allChords, func, ticksPerSec);
@ -305,8 +306,8 @@ void findBeatLocations(
"MidiBeat::findBeatLocations", "Wrong count of bar levels");
// beat set - first case
HumanBeatData beatData = prepareHumanBeatData(beatTimes, allChords,
ticksPerSec, beatsInBar);
MidiOperations::HumanBeatData beatData = prepareHumanBeatData(
beatTimes, allChords, ticksPerSec, beatsInBar);
beatData.timeSig = barFraction;
double matchRank = findMatchRank(beatData.beatSet, events,
levels, beatsInBar, ticksPerSec);
@ -319,14 +320,19 @@ void findBeatLocations(
beatResults.insert({matchRank, beatData});
}
}
auto *data = preferences.midiImportOperations.data();
if (!beatResults.empty()) {
const HumanBeatData &beatData = beatResults.begin()->second;
const MidiOperations::HumanBeatData &beatData = beatResults.begin()->second;
setTimeSig(sigmap, beatData.timeSig);
preferences.midiImportOperations.setHumanBeats(beatData);
data->humanBeatData = beatData;
}
else {
const auto currentTimeSig = ReducedFraction(sigmap->timesig(0).timesig());
preferences.midiImportOperations.setTimeSignature(currentTimeSig);
data->trackOpers.timeSigNumerator = Meter::fractionNumeratorToUserValue(
currentTimeSig.numerator());
data->trackOpers.timeSigDenominator = Meter::fractionDenominatorToUserValue(
currentTimeSig.denominator());
}
}
@ -375,13 +381,13 @@ void adjustChordsToBeats(std::multimap<int, MTrack> &tracks,
ReducedFraction &lastTick)
{
const auto &opers = preferences.midiImportOperations;
const std::set<ReducedFraction> *beats = opers.getHumanBeats();
if (!beats || beats->empty())
const std::set<ReducedFraction> &beats = opers.data()->humanBeatData.beatSet;
if (beats.empty())
return;
if (opers.trackOperations(0).quantize.humanPerformance) {
if (opers.data()->trackOpers.isHumanPerformance) {
Q_ASSERT_X(beats->size() > 1, "MidiBeat::adjustChordsToBeats", "Human beat count < 2");
Q_ASSERT_X(beats.size() > 1, "MidiBeat::adjustChordsToBeats", "Human beat count < 2");
const auto newBeatLen = ReducedFraction::fromTicks(MScore::division);
for (auto trackIt = tracks.begin(); trackIt != tracks.end(); ++trackIt) {
@ -393,11 +399,11 @@ void adjustChordsToBeats(std::multimap<int, MTrack> &tracks,
lastTick = {0, 1};
auto chordIt = chords.begin();
auto it = beats->begin();
auto it = beats.begin();
auto beatStart = *it;
auto newBeatStart = ReducedFraction(0, 1);
for (++it; it != beats->end(); ++it) {
for (++it; it != beats.end(); ++it) {
const auto &beatEnd = *it;
Q_ASSERT_X(beatEnd > beatStart, "MidiBeat::adjustChordsToBeats",
@ -410,7 +416,7 @@ void adjustChordsToBeats(std::multimap<int, MTrack> &tracks,
// quantize to prevent ReducedFraction overflow
newOnTimeInBeat = Quantize::quantizeValue(
newOnTimeInBeat, MChord::minAllowedDuration());
scaleOffTimes(chordIt->second.notes, *beats, it,
scaleOffTimes(chordIt->second.notes, beats, it,
newBeatStart, newBeatLen, lastTick);
const auto newOnTime = newBeatStart + newOnTimeInBeat;
newChords.insert({newOnTime, chordIt->second});
@ -430,20 +436,13 @@ void adjustChordsToBeats(std::multimap<int, MTrack> &tracks,
void setTimeSignature(TimeSigMap *sigmap)
{
auto &opers = preferences.midiImportOperations;
const auto currentTimeSig = opers.timeSignature();
// when opened MIDI file was not detected as human-performed
if (currentTimeSig == ReducedFraction(0, 1))
return;
const auto newTimeSig = Meter::userTimeSigToFraction(opers.currentTrackOperations().timeSig);
setTimeSig(sigmap, newTimeSig);
const auto *beats = opers.getHumanBeats();
if (!beats || beats->empty() || newTimeSig == currentTimeSig)
return;
opers.setTimeSignature(newTimeSig);
const auto *data = preferences.midiImportOperations.data();
const std::set<ReducedFraction> &beats = data->humanBeatData.beatSet;
if (beats.empty())
return; // don't set time sig for non-human performed MIDI files
const auto timeSig = Meter::userTimeSigToFraction(data->trackOpers.timeSigNumerator,
data->trackOpers.timeSigDenominator);
setTimeSig(sigmap, timeSig);
}
} // namespace MidiBeat

View file

@ -2,6 +2,7 @@
#include "importmidi_inner.h"
#include "importmidi_chord.h"
#include "importmidi_clef.h"
#include "importmidi_operations.h"
#include "libmscore/mscore.h"
#include "preferences.h"
@ -196,11 +197,10 @@ void collectChords(std::multimap<int, MTrack> &tracks)
if (chords.empty())
continue;
const int trackIndex = track.second.indexOfOperation;
const auto opers = preferences.midiImportOperations.trackOperations(trackIndex);
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const auto minAllowedDur = minAllowedDuration();
const auto threshTime = (opers.quantize.humanPerformance)
const auto threshTime = (opers.isHumanPerformance)
? minAllowedDur * 2 : minAllowedDur / 2;
const auto fudgeTime = threshTime / 4;
const auto threshExtTime = threshTime / 2;

View file

@ -21,8 +21,11 @@
#include "libmscore/note.h"
#include "libmscore/slur.h"
#include "libmscore/element.h"
#include "libmscore/sig.h"
#include "importmidi_tie.h"
#include "importmidi_meter.h"
#include "importmidi_fraction.h"
#include "importmidi_operations.h"
#include "preferences.h"
#include <set>
@ -398,9 +401,9 @@ void createClefs(Staff *staff, int indexOfOperation, bool isDrumTrack)
ClefType mainClef = staff->clefTypeList(0)._concertClef;
const int strack = staff->idx() * VOICES;
const auto trackOpers = preferences.midiImportOperations.trackOperations(indexOfOperation);
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
if (trackOpers.changeClef) {
if (opers.changeClef.value(indexOfOperation)) {
MidiTie::TieStateMachine tieTracker;
// find optimal clef changes via dynamic programming

View file

@ -1,234 +0,0 @@
#include "importmidi_data.h"
#include "importmidi_operations.h"
#include "importmidi_meter.h"
#include "importmidi_inner.h"
#include "importmidi_beat.h"
namespace Ms {
void MidiData::setTracksData(const QString &fileName, const QList<TrackData> &tracksData)
{
data[fileName].tracksData = tracksData;
}
void MidiData::saveHHeaderState(const QString &fileName, const QByteArray &headerData)
{
data[fileName].HHeaderData = headerData;
}
void MidiData::saveVHeaderState(const QString &fileName, const QByteArray &headerData)
{
data[fileName].VHeaderData = headerData;
}
void MidiData::excludeFile(const QString &fileName)
{
data.remove(fileName);
}
QList<TrackData> MidiData::tracksData(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return QList<TrackData>();
return it.value().tracksData;
}
QByteArray MidiData::HHeaderData(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return QByteArray();
return it.value().HHeaderData;
}
QByteArray MidiData::VHeaderData(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return QByteArray();
return it.value().VHeaderData;
}
int MidiData::selectedRow(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return 0;
return it.value().selectedRow;
}
void MidiData::setSelectedRow(const QString &fileName, int row)
{
data[fileName].selectedRow = row;
}
void MidiData::setMidiFile(const QString &fileName, const MidiFile &midiFile)
{
data[fileName].midiFile = midiFile;
}
const MidiFile* MidiData::midiFile(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return nullptr;
return &(it.value().midiFile);
}
void MidiData::addTrackLyrics(const QString &fileName,
const std::multimap<ReducedFraction, std::string> &trackLyrics)
{
data[fileName].lyricTracks.push_back(trackLyrics);
}
const QList<std::multimap<ReducedFraction, std::string> >*
MidiData::getLyrics(const QString &fileName)
{
const auto it = data.find(fileName);
if (it == data.end())
return nullptr;
return &it.value().lyricTracks;
}
QString MidiData::charset(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return "";
return it.value().charset;
}
void MidiData::setCharset(const QString &fileName, const QString &charset)
{
const auto it = data.find(fileName);
if (it == data.end())
return;
it.value().charset = charset;
}
bool MidiData::isHumanPerformance(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return false;
return it.value().isHumanPerformance;
}
void MidiData::setHumanPerformance(const QString &fileName, bool value)
{
const auto it = data.find(fileName);
if (it == data.end())
return;
it.value().isHumanPerformance = value;
}
MidiOperation::QuantValue MidiData::quantValue(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return Quantization().value;
return it.value().quantValue;
}
void MidiData::setQuantValue(const QString &fileName, MidiOperation::QuantValue value)
{
const auto it = data.find(fileName);
if (it == data.end())
return;
it.value().quantValue = value;
}
bool MidiData::needToSplit(const QString &fileName, int trackIndex) const
{
const auto it = data.find(fileName);
if (it == data.end())
return false;
const auto fit = it.value().needLRhandSplit.find(trackIndex);
if (fit == it.value().needLRhandSplit.end())
return false;
return fit->second;
}
void MidiData::setNeedToSplit(const QString &fileName, int trackIndex, bool value)
{
const auto it = data.find(fileName);
if (it == data.end())
return;
it.value().needLRhandSplit[trackIndex] = value;
}
MidiOperation::QuantValue MidiData::defaultQuantValue()
{
return Quantization().value;
}
const std::set<ReducedFraction>*
MidiData::getHumanBeats(const QString &fileName) const
{
const auto it = data.find(fileName);
if (it == data.end())
return nullptr;
return &it.value().humanBeatData.beatSet;
}
void MidiData::setHumanBeats(const QString &fileName, const HumanBeatData &beatData)
{
const auto it = data.find(fileName);
if (it == data.end())
return;
it.value().humanBeatData = beatData;
}
ReducedFraction MidiData::timeSignature(const QString &fileName) const
{
const auto it = data.find(fileName);
// if data was not found - return zero value;
// it means that time sig should be taken from MIDI file meta event
// or, if no such event, it will be set to default 4/4 later;
// so, don't return default time sig here
if (it == data.end())
return ReducedFraction(0, 1);
return it.value().humanBeatData.timeSig;
}
void MidiData::setTimeSignature(const QString &fileName, const ReducedFraction &timeSig)
{
const auto it = data.find(fileName);
if (it == data.end())
return;
HumanBeatData &beatData = it.value().humanBeatData;
if (beatData.timeSig == timeSig)
return;
beatData.timeSig = timeSig;
auto &beatSet = beatData.beatSet;
if (beatSet.empty())
return;
for (int i = 0; i != beatData.addedFirstBeats; ++i) {
Q_ASSERT_X(!beatSet.empty(), "MidiData::setTimeSignature",
"Empty beat set after deletion first beats");
beatSet.erase(beatSet.begin());
}
for (int i = 0; i != beatData.addedLastBeats; ++i) {
Q_ASSERT_X(!beatSet.empty(), "MidiData::setTimeSignature",
"Empty beat set after deletion last beats");
beatSet.erase(std::prev(beatSet.end()));
}
const int beatsInBar = MidiBeat::beatsInBar(timeSig);
MidiBeat::addFirstBeats(beatSet, beatData.firstChordTick,
beatsInBar, beatData.addedFirstBeats);
MidiBeat::addLastBeats(beatSet, beatData.lastChordTick,
beatsInBar, beatData.addedLastBeats);
}
} // namespace Ms

View file

@ -1,90 +0,0 @@
#ifndef IMPORTMIDI_DATA_H
#define IMPORTMIDI_DATA_H
#include "midi/midifile.h"
#include "importmidi_fraction.h"
#include "importmidi_operation.h"
#include <set>
namespace Ms {
namespace MidiCharset {
QString defaultCharset();
}
struct TrackData;
class MidiData
{
public:
void setTracksData(const QString &fileName, const QList<TrackData> &tracksData);
void saveHHeaderState(const QString &fileName, const QByteArray &headerData);
void saveVHeaderState(const QString &fileName, const QByteArray &headerData);
void excludeFile(const QString &fileName);
QList<TrackData> tracksData(const QString &fileName) const;
QByteArray HHeaderData(const QString &fileName) const;
QByteArray VHeaderData(const QString &fileName) const;
int selectedRow(const QString &fileName) const;
void setSelectedRow(const QString &fileName, int row);
void setMidiFile(const QString &fileName, const MidiFile &midiFile);
const MidiFile *midiFile(const QString &fileName) const;
// lyrics
void addTrackLyrics(const QString &fileName,
const std::multimap<ReducedFraction, std::string> &trackLyrics);
const QList<std::multimap<ReducedFraction, std::string> >*
getLyrics(const QString &fileName);
QString charset(const QString &fileName) const;
void setCharset(const QString &fileName, const QString &charset);
// human performance: is MIDI unaligned
bool isHumanPerformance(const QString &fileName) const;
void setHumanPerformance(const QString &fileName, bool value);
const std::set<ReducedFraction>* getHumanBeats(const QString &fileName) const;
void setHumanBeats(const QString &fileName, const HumanBeatData &beatData);
// time sig can be zero (not specified) if, when opened,
// MIDI file was not detected as human-performed
ReducedFraction timeSignature(const QString &fileName) const;
void setTimeSignature(const QString &fileName, const ReducedFraction &value);
// quantization
MidiOperation::QuantValue quantValue(const QString &fileName) const;
void setQuantValue(const QString &fileName, MidiOperation::QuantValue value);
// left/right hand separation
bool needToSplit(const QString &fileName, int trackIndex) const;
void setNeedToSplit(const QString &fileName, int trackIndex, bool value);
private:
static MidiOperation::QuantValue defaultQuantValue();
struct MidiDataStore
{
QByteArray HHeaderData;
QByteArray VHeaderData;
QList<TrackData> tracksData;
// tracks of <tick, lyric fragment> from karaoke files
// QList of lyric tracks - there can be multiple lyric tracks,
// lyric track count != MIDI chord track count in general
QList<std::multimap<ReducedFraction, std::string>> lyricTracks;
// default values - when MIDI is opened
int selectedRow = 0;
MidiFile midiFile;
QString charset = MidiCharset::defaultCharset();
bool isHumanPerformance = false;
std::map<int, bool> needLRhandSplit; // <track index, value = false by default>
MidiOperation::QuantValue quantValue = defaultQuantValue();
// human performance
HumanBeatData humanBeatData;
};
QMap<QString, MidiDataStore> data; // <file name, tracks data>
};
} // namespace Ms
#endif // IMPORTMIDI_DATA_H

View file

@ -1,5 +1,4 @@
#include "importmidi_opdelegate.h"
#include "importmidi_opmodel.h"
#include "importmidi_delegate.h"
namespace Ms {

View file

@ -1,5 +1,5 @@
#ifndef IMPORTMIDI_OPDELEGATE_H
#define IMPORTMIDI_OPDELEGATE_H
#ifndef IMPORTMIDI_DELEGATE_H
#define IMPORTMIDI_DELEGATE_H
namespace Ms {
@ -31,4 +31,4 @@ class OperationsDelegate : public QStyledItemDelegate
} // namespace Ms
#endif // IMPORTMIDI_OPDELEGATE_H
#endif // IMPORTMIDI_DELEGATE_H

View file

@ -5,7 +5,9 @@
#include "libmscore/drumset.h"
#include "importmidi_chord.h"
#include "importmidi_tuplet.h"
#include "importmidi_operations.h"
#include "libmscore/score.h"
#include "midi/midifile.h"
#include <set>
@ -156,9 +158,8 @@ void splitDrumTracks(std::multimap<int, MTrack> &tracks)
for (auto it = tracks.begin(); it != tracks.end(); ++it) {
if (!it->second.mtrack->drumTrack())
continue;
const auto operations = preferences.midiImportOperations.trackOperations(
it->second.indexOfOperation);
if (!operations.splitDrums.doSplit)
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
if (!opers.doStaffSplit.value(it->second.indexOfOperation))
continue;
const auto newTracks = splitDrumTrack(it->second);
const int trackIndex = it->first;
@ -185,16 +186,13 @@ void setStaffBracketForDrums(QList<MTrack> &tracks)
int counter = 0;
Staff *firstDrumStaff = nullptr;
int opIndex = -1;
const auto &opers = preferences.midiImportOperations;
for (const MTrack &track: tracks) {
if (track.mtrack->drumTrack()) {
if (opIndex != track.indexOfOperation) {
opIndex = track.indexOfOperation;
setBracket(firstDrumStaff, counter);
if (opers.trackOperations(track.indexOfOperation).splitDrums.showStaffBracket) {
opIndex = track.indexOfOperation;
firstDrumStaff = track.staff;
}
firstDrumStaff = track.staff;
}
++counter;
}
@ -236,13 +234,13 @@ ReducedFraction findOptimalNoteOffTime(
void removeRests(std::multimap<int, MTrack> &tracks, const TimeSigMap *sigmap)
{
const auto &opers = preferences.midiImportOperations;
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
for (auto &trackItem: tracks) {
MTrack &track = trackItem.second;
if (!track.mtrack->drumTrack())
continue;
if (!opers.trackOperations(track.indexOfOperation).removeDrumRests)
if (!opers.simplifyDurations.value(track.indexOfOperation))
continue;
bool changed = false;
// all chords here with the same voice should have different onTime values

View file

@ -1,65 +1,69 @@
#include "importmidi_inner.h"
#include "importmidi_operations.h"
#include "preferences.h"
#include "libmscore/durationtype.h"
#include "midi/midifile.h"
namespace Ms {
namespace Meter {
ReducedFraction userTimeSigToFraction(const MidiTimeSig &timeSig)
ReducedFraction userTimeSigToFraction(
MidiOperations::TimeSigNumerator timeSigNumerator,
MidiOperations::TimeSigDenominator timeSigDenominator)
{
int numerator = 4;
int denominator = 4;
switch (timeSig.numerator) {
case MidiOperation::TimeSigNumerator::_2:
switch (timeSigNumerator) {
case MidiOperations::TimeSigNumerator::_2:
numerator = 2;
break;
case MidiOperation::TimeSigNumerator::_3:
case MidiOperations::TimeSigNumerator::_3:
numerator = 3;
break;
case MidiOperation::TimeSigNumerator::_4:
case MidiOperations::TimeSigNumerator::_4:
numerator = 4;
break;
case MidiOperation::TimeSigNumerator::_5:
case MidiOperations::TimeSigNumerator::_5:
numerator = 5;
break;
case MidiOperation::TimeSigNumerator::_6:
case MidiOperations::TimeSigNumerator::_6:
numerator = 6;
break;
case MidiOperation::TimeSigNumerator::_7:
case MidiOperations::TimeSigNumerator::_7:
numerator = 7;
break;
case MidiOperation::TimeSigNumerator::_9:
case MidiOperations::TimeSigNumerator::_9:
numerator = 9;
break;
case MidiOperation::TimeSigNumerator::_12:
case MidiOperations::TimeSigNumerator::_12:
numerator = 12;
break;
case MidiOperation::TimeSigNumerator::_15:
case MidiOperations::TimeSigNumerator::_15:
numerator = 15;
break;
case MidiOperation::TimeSigNumerator::_21:
case MidiOperations::TimeSigNumerator::_21:
numerator = 21;
break;
default:
break;
}
switch (timeSig.denominator) {
case MidiOperation::TimeSigDenominator::_2:
switch (timeSigDenominator) {
case MidiOperations::TimeSigDenominator::_2:
denominator = 2;
break;
case MidiOperation::TimeSigDenominator::_4:
case MidiOperations::TimeSigDenominator::_4:
denominator = 4;
break;
case MidiOperation::TimeSigDenominator::_8:
case MidiOperations::TimeSigDenominator::_8:
denominator = 8;
break;
case MidiOperation::TimeSigDenominator::_16:
case MidiOperations::TimeSigDenominator::_16:
denominator = 16;
break;
case MidiOperation::TimeSigDenominator::_32:
case MidiOperations::TimeSigDenominator::_32:
denominator = 32;
break;
default:
@ -69,50 +73,50 @@ ReducedFraction userTimeSigToFraction(const MidiTimeSig &timeSig)
return ReducedFraction(numerator, denominator);
}
MidiOperation::TimeSigNumerator fractionNumeratorToUserValue(int n)
MidiOperations::TimeSigNumerator fractionNumeratorToUserValue(int n)
{
MidiOperation::TimeSigNumerator numerator = MidiOperation::TimeSigNumerator::_4;
MidiOperations::TimeSigNumerator numerator = MidiOperations::TimeSigNumerator::_4;
if (n == 2)
numerator = MidiOperation::TimeSigNumerator::_2;
numerator = MidiOperations::TimeSigNumerator::_2;
else if (n == 3)
numerator = MidiOperation::TimeSigNumerator::_3;
numerator = MidiOperations::TimeSigNumerator::_3;
else if (n == 4)
numerator = MidiOperation::TimeSigNumerator::_4;
numerator = MidiOperations::TimeSigNumerator::_4;
else if (n == 5)
numerator = MidiOperation::TimeSigNumerator::_5;
numerator = MidiOperations::TimeSigNumerator::_5;
else if (n == 6)
numerator = MidiOperation::TimeSigNumerator::_6;
numerator = MidiOperations::TimeSigNumerator::_6;
else if (n == 7)
numerator = MidiOperation::TimeSigNumerator::_7;
numerator = MidiOperations::TimeSigNumerator::_7;
else if (n == 9)
numerator = MidiOperation::TimeSigNumerator::_9;
numerator = MidiOperations::TimeSigNumerator::_9;
else if (n == 12)
numerator = MidiOperation::TimeSigNumerator::_12;
numerator = MidiOperations::TimeSigNumerator::_12;
else if (n == 15)
numerator = MidiOperation::TimeSigNumerator::_15;
numerator = MidiOperations::TimeSigNumerator::_15;
else if (n == 21)
numerator = MidiOperation::TimeSigNumerator::_21;
numerator = MidiOperations::TimeSigNumerator::_21;
else
Q_ASSERT_X(false, "Meter::fractionNumeratorToUserValue", "Unknown numerator");
return numerator;
}
MidiOperation::TimeSigDenominator fractionDenominatorToUserValue(int z)
MidiOperations::TimeSigDenominator fractionDenominatorToUserValue(int z)
{
MidiOperation::TimeSigDenominator denominator = MidiOperation::TimeSigDenominator::_4;
MidiOperations::TimeSigDenominator denominator = MidiOperations::TimeSigDenominator::_4;
if (z == 2)
denominator = MidiOperation::TimeSigDenominator::_2;
denominator = MidiOperations::TimeSigDenominator::_2;
else if (z == 4)
denominator = MidiOperation::TimeSigDenominator::_4;
denominator = MidiOperations::TimeSigDenominator::_4;
else if (z == 8)
denominator = MidiOperation::TimeSigDenominator::_8;
denominator = MidiOperations::TimeSigDenominator::_8;
else if (z == 16)
denominator = MidiOperation::TimeSigDenominator::_16;
denominator = MidiOperations::TimeSigDenominator::_16;
else if (z == 32)
denominator = MidiOperation::TimeSigDenominator::_32;
denominator = MidiOperations::TimeSigDenominator::_32;
else
Q_ASSERT_X(false, "Meter::fractionDenominatorToUserValue", "Unknown denominator");
@ -147,7 +151,7 @@ namespace MidiCharset {
QString convertToCharset(const std::string &text)
{
// charset for the current MIDI file
QString charset = preferences.midiImportOperations.charset();
QString charset = preferences.midiImportOperations.data()->charset;
auto *codec = QTextCodec::codecForName(charset.toLatin1());
if (codec)
return codec->toUnicode(text.c_str());

View file

@ -49,9 +49,11 @@ struct DivisionInfo
enum class DurationType : char;
ReducedFraction userTimeSigToFraction(const MidiTimeSig &timeSig);
MidiOperation::TimeSigNumerator fractionNumeratorToUserValue(int n);
MidiOperation::TimeSigDenominator fractionDenominatorToUserValue(int z);
ReducedFraction userTimeSigToFraction(
MidiOperations::TimeSigNumerator timeSigNumerator,
MidiOperations::TimeSigDenominator timeSigDenominator);
MidiOperations::TimeSigNumerator fractionNumeratorToUserValue(int n);
MidiOperations::TimeSigDenominator fractionDenominatorToUserValue(int z);
} // namespace Meter

View file

@ -2,6 +2,7 @@
#include "importmidi_inner.h"
#include "importmidi_fraction.h"
#include "importmidi_chord.h"
#include "importmidi_operations.h"
#include "preferences.h"
@ -386,9 +387,9 @@ void splitByFixedPitch(std::multimap<int, MTrack> &tracks,
std::multimap<int, MTrack>::iterator &it)
{
auto &srcTrack = it->second;
const auto trackOpers = preferences.midiImportOperations.trackOperations(srcTrack.indexOfOperation);
const int splitPitch = 12 * (int)trackOpers.LHRH.splitPitchOctave
+ (int)trackOpers.LHRH.splitPitchNote;
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int splitPitch = 12 * (int)opers.staffSplitOctave.value(srcTrack.indexOfOperation)
+ (int)opers.staffSplitNote.value(srcTrack.indexOfOperation);
std::multimap<ReducedFraction, MidiChord> leftHandChords;
for (auto i = srcTrack.chords.begin(); i != srcTrack.chords.end(); ) {
@ -420,20 +421,19 @@ void splitIntoLeftRightHands(std::multimap<int, MTrack> &tracks)
for (auto it = tracks.begin(); it != tracks.end(); ++it) {
if (it->second.mtrack->drumTrack())
continue;
const auto operations = preferences.midiImportOperations.trackOperations(
it->second.indexOfOperation);
if (!operations.LHRH.doIt)
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
if (!opers.doStaffSplit.value(it->second.indexOfOperation))
continue;
// iterator 'it' will change after track split to ++it
// C++11 guarantees that newely inserted item with equal key will go after:
// "The relative ordering of elements with equivalent keys is preserved,
// and newly inserted elements follow those with equivalent keys
// already in the container"
switch (operations.LHRH.method) {
case MidiOperation::LHRHMethod::HAND_WIDTH:
switch (opers.staffSplitMethod.value(it->second.indexOfOperation)) {
case MidiOperations::StaffSplitMethod::HAND_WIDTH:
splitByHandWidth(tracks, it);
break;
case MidiOperation::LHRHMethod::SPECIFIED_PITCH:
case MidiOperations::StaffSplitMethod::SPECIFIED_PITCH:
splitByFixedPitch(tracks, it);
break;
}

View file

@ -2,6 +2,7 @@
#include "importmidi_inner.h"
#include "importmidi_fraction.h"
#include "importmidi_chord.h"
#include "importmidi_operations.h"
#include "libmscore/box.h"
#include "libmscore/element.h"
#include "libmscore/measurebase.h"
@ -171,19 +172,19 @@ void extractLyricsToMidiData(const MidiFile *mf)
for (const auto &t: mf->tracks()) {
const auto lyrics = extractLyricsFromTrack(t, mf->division());
if (!lyrics.empty())
preferences.midiImportOperations.addTrackLyrics(lyrics);
preferences.midiImportOperations.data()->lyricTracks.push_back(lyrics);
}
}
void assignLyricsToTracks(QList<MTrack> &tracks)
{
std::set<int> usedTracks;
const auto *lyricTracks = preferences.midiImportOperations.getLyrics();
if (!lyricTracks)
const auto &lyricTracks = preferences.midiImportOperations.data()->lyricTracks;
if (lyricTracks.isEmpty())
return;
for (int i = 0; i != lyricTracks->size(); ++i) {
const BestTrack bestTrack = findBestTrack(tracks, (*lyricTracks)[i], usedTracks);
for (int i = 0; i != lyricTracks.size(); ++i) {
const BestTrack bestTrack = findBestTrack(tracks, lyricTracks[i], usedTracks);
if (bestTrack.index >= 0) {
usedTracks.insert(bestTrack.index);
tracks[bestTrack.index].initLyricTrackIndex = i;
@ -194,11 +195,11 @@ void assignLyricsToTracks(QList<MTrack> &tracks)
void setInitialLyricsFromMidiData(const QList<MTrack> &tracks)
{
std::set<int> usedTracks;
const auto *lyricTracks = preferences.midiImportOperations.getLyrics();
if (!lyricTracks)
const auto &lyricTracks = preferences.midiImportOperations.data()->lyricTracks;
if (lyricTracks.isEmpty())
return;
for (const auto &lyricTrack: *lyricTracks) {
for (const auto &lyricTrack: lyricTracks) {
const BestTrack bestTrack = findBestTrack(tracks, lyricTrack, usedTracks);
if (bestTrack.index >= 0) {
usedTracks.insert(bestTrack.index);
@ -228,14 +229,14 @@ std::vector<std::pair<ReducedFraction, ReducedFraction> > findMatchedLyricTimes(
void setLyricsFromOperations(const QList<MTrack> &tracks)
{
const auto *lyricTracks = preferences.midiImportOperations.getLyrics();
if (!lyricTracks)
const auto &lyricTracks = preferences.midiImportOperations.data()->lyricTracks;
if (lyricTracks.isEmpty())
return;
for (const auto &track: tracks) {
const auto opers = preferences.midiImportOperations.trackOperations(
track.indexOfOperation);
if (opers.lyricTrackIndex >= 0 && opers.lyricTrackIndex < lyricTracks->size()) {
const auto &lyricTrack = (*lyricTracks)[opers.lyricTrackIndex];
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int lyricTrackIndex = opers.lyricTrackIndex.value(track.indexOfOperation);
if (lyricTrackIndex >= 0 && lyricTrackIndex < lyricTracks.size()) {
const auto &lyricTrack = lyricTracks[lyricTrackIndex];
const auto matchedLyricTimes = findMatchedLyricTimes(track.chords, lyricTrack);
addLyricsToScore(lyricTrack, matchedLyricTimes, track.staff);
@ -245,7 +246,8 @@ void setLyricsFromOperations(const QList<MTrack> &tracks)
void setLyricsToScore(const QList<MTrack> &tracks)
{
if (preferences.midiImportOperations.count() == 0)
const auto *data = preferences.midiImportOperations.data();
if (data->isNewlyOpened)
setInitialLyricsFromMidiData(tracks);
else
setLyricsFromOperations(tracks);
@ -254,12 +256,13 @@ void setLyricsToScore(const QList<MTrack> &tracks)
QList<std::string> makeLyricsListForUI()
{
QList<std::string> list;
const auto *lyrics = preferences.midiImportOperations.getLyrics();
if (!lyrics)
const auto &lyrics = preferences.midiImportOperations.data()->lyricTracks;
if (lyrics.isEmpty())
return list;
const unsigned int symbolLimit = 16;
for (const auto &trackLyric: *lyrics) {
for (const auto &trackLyric: lyrics) {
std::string lyricText;
for (const auto &lyric: trackLyric) {

403
mscore/importmidi_model.cpp Normal file
View file

@ -0,0 +1,403 @@
#include "importmidi_model.h"
//struct TrackCol {
// enum {
// DO_IMPORT = 0,
// STAFF_NAME,
// INSTRUMENT,
// LYRICS,
// QUANTIZATION,
// HUMAN,
// VOICE_COUNT,
// TUPLETS,
// SPLIT_STAFF,
// CHANGE_CLEF,
// SIMPLIFY,
// STACCATO,
// USE_DOTS,
// TIME_SIG,
// PICKUP_BAR,
// SWING,
// COL_COUNT
// };
// };
namespace Ms {
class TracksModel::Column
{
public:
Column(MidiOperations::Opers &opers)
: _opers(opers), _isEditable(true), _hasCharset(false)
{}
virtual ~Column() {}
virtual QVariant value(int trackIndex) const = 0;
virtual void setValue(const QVariant &value, int trackIndex) = 0;
virtual QString headerName() const = 0;
virtual QStringList valueList() const { return _values; }
bool isEditable() const { return _isEditable; }
bool hasCharset() const { return _hasCharset; }
protected:
MidiOperations::Opers &_opers;
QStringList _values;
bool _isEditable;
bool _hasCharset;
};
TracksModel::TracksModel()
: _trackCount(0)
, _frozenColCount(0)
{
}
TracksModel::~TracksModel()
{
}
void TracksModel::reset(const MidiOperations::Opers &opers,
const QList<std::string> &lyricsList,
int trackCount)
{
_trackOpers = opers;
_columns.clear();
_trackCount = trackCount;
_frozenColCount = 0;
if (trackCount == 0)
return;
//-----------------------------------------------------------------------
struct Import : Column {
Import(MidiOperations::Opers &opers) : Column(opers) {}
QString headerName() const { return "Import"; }
QVariant value(int trackIndex) const
{
return _opers.doImport.value(trackIndex);
}
void setValue(const QVariant &value, int trackIndex)
{
_opers.doImport.setValue(trackIndex, value.toBool());
}
};
++_frozenColCount;
_columns.push_back(std::unique_ptr<Column>(new Import(_trackOpers)));
//-----------------------------------------------------------------------
struct InstrumentName : Column {
InstrumentName(MidiOperations::Opers &opers) : Column(opers)
{
_isEditable = false;
}
QString headerName() const { return "Sound"; }
QVariant value(int trackIndex) const
{
return _opers.instrumentName.value(trackIndex);
}
void setValue(const QVariant &/*value*/, int /*trackIndex*/) {}
};
++_frozenColCount;
_columns.push_back(std::unique_ptr<Column>(new InstrumentName(_trackOpers)));
//-----------------------------------------------------------------------
bool hasStaffName = false;
for (int i = 0; i != _trackCount; ++i) {
if (_trackOpers.staffName.value(i) != "") {
hasStaffName = true;
break;
}
}
if (hasStaffName) {
struct StaffName : Column {
StaffName(MidiOperations::Opers &opers) : Column(opers)
{
_isEditable = false;
_hasCharset = true;
}
QString headerName() const { return "Staff name"; }
QVariant value(int trackIndex) const
{
return MidiCharset::convertToCharset(_opers.staffName.value(trackIndex));
}
void setValue(const QVariant &/*value*/, int /*trackIndex*/) {}
};
++_frozenColCount;
_columns.push_back(std::unique_ptr<Column>(new StaffName(_trackOpers)));
}
//-----------------------------------------------------------------------
if (!lyricsList.isEmpty()) {
struct Lyrics : Column {
Lyrics(MidiOperations::Opers &opers, const QList<std::string> &lyricsList)
: Column(opers), _lyricsList(lyricsList)
{
_hasCharset = true;
}
QString headerName() const { return "Lyrics"; }
QVariant value(int trackIndex) const
{
int index = _opers.lyricTrackIndex.value(trackIndex);
if (index >= 0)
return MidiCharset::convertToCharset(_lyricsList[index]);
return "";
}
void setValue(const QVariant &value, int trackIndex)
{
_opers.lyricTrackIndex.setValue(trackIndex, value.toInt());
}
QStringList valueList() const
{
auto list = QStringList("");
for (const auto &lyric: _lyricsList)
list.append(MidiCharset::convertToCharset(lyric));
return list;
}
private:
QList<std::string> _lyricsList;
};
_columns.push_back(std::unique_ptr<Column>(new Lyrics(_trackOpers, lyricsList)));
}
//-----------------------------------------------------------------------
struct QuantValue : Column {
QuantValue(MidiOperations::Opers &opers) : Column(opers)
{
_values.push_back("Default");
_values.push_back("Quarter");
_values.push_back("Eighth");
_values.push_back("16th");
_values.push_back("32nd");
_values.push_back("64th");
_values.push_back("128th");
}
QString headerName() const { return "Quantization"; }
QVariant value(int trackIndex) const
{
return _values[(int)_opers.quantValue.value(trackIndex)];
}
void setValue(const QVariant &value, int trackIndex)
{
_opers.quantValue.setValue(trackIndex, (MidiOperations::QuantValue)value.toInt());
}
};
_columns.push_back(std::unique_ptr<Column>(new QuantValue(_trackOpers)));
}
void TracksModel::clear()
{
beginResetModel();
_trackCount = 0;
_trackOpers = MidiOperations::Opers();
_columns.clear();
endResetModel();
}
const MidiOperations::Opers& TracksModel::trackOpers() const
{
return _trackOpers;
}
void TracksModel::setTrackShuffleIndex(int trackIndex, int newIndex)
{
if (!isTrackIndexValid(trackIndex))
return;
_trackOpers.trackIndexAfterShuffle.setValue(trackIndex, newIndex);
}
void TracksModel::updateCharset()
{
for (int i = 0; i != (int)_columns.size(); ++i) {
if (_columns[i]->hasCharset())
forceColumnDataChanged(i);
}
}
int TracksModel::rowFromTrackIndex(int trackIndex) const
{
// first row reserved for all tracks if track count > 1
return (_trackCount > 1) ? trackIndex + 1 : trackIndex;
}
int TracksModel::trackIndexFromRow(int row) const
{
// first row reserved for all tracks if track count > 1
// return -1 if row is all tracks row
return (_trackCount > 1) ? row - 1 : row;
}
int TracksModel::trackCountForImport() const
{
int count = 0;
for (int i = 0; i != _trackCount; ++i) {
if (_trackOpers.doImport.value(i))
++count;
}
return count;
}
int TracksModel::frozenRowCount() const
{
if (_trackCount > 1)
return 1;
return 0;
}
int TracksModel::frozenColCount() const
{
return _frozenColCount;
}
int TracksModel::rowCount(const QModelIndex &/*parent*/) const
{
return (_trackCount > 1) ? _trackCount + 1 : _trackCount;
}
int TracksModel::columnCount(const QModelIndex &/*parent*/) const
{
return _columns.size();
}
QVariant TracksModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
const int trackIndex = trackIndexFromRow(index.row());
if (!isTrackIndexValid(trackIndex) || !isColumnValid(index.column()))
return QVariant();
switch (role) {
case Qt::DisplayRole:
if (trackIndex == -1) { // all tracks
QVariant value = _columns[index.column()]->value(0);
if (value.type() == QVariant::String) {
for (int i = 1; i < _trackCount; ++i) {
if (_columns[index.column()]->value(i).toString() != value.toString())
return "...";
}
return value.toString();
}
}
else {
QVariant value = _columns[index.column()]->value(trackIndex);
if (value.type() == QVariant::String)
return value.toString();
}
break;
case Qt::EditRole:
if (_columns[index.column()]->isEditable()) {
QVariant value = _columns[index.column()]->value(trackIndex);
if (value.type() == QVariant::StringList)
return _columns[index.column()]->valueList();
}
break;
case Qt::CheckStateRole:
if (trackIndex == -1) {
QVariant value = _columns[index.column()]->value(0);
if (value.type() == QVariant::Bool) {
for (int i = 1; i < _trackCount; ++i) {
if (_columns[index.column()]->value(i).toBool() != value.toBool())
return Qt::PartiallyChecked;
}
return (value.toBool()) ? Qt::Checked : Qt::Unchecked;
}
}
else {
QVariant value = _columns[index.column()]->value(trackIndex);
if (value.type() == QVariant::Bool)
return (value.toBool()) ? Qt::Checked : Qt::Unchecked;
}
break;
case Qt::TextAlignmentRole:
// if (!columns[index.column()]->valuesList.empty())
// return Qt::AlignLeft + Qt::AlignVCenter;
return Qt::AlignCenter;
break;
case Qt::ToolTipRole:
if (trackIndex != -1) {
QVariant value = _columns[index.column()]->value(trackIndex);
if (value.type() == QVariant::String
&& _columns[index.column()]->valueList().empty()) {
return MidiCharset::convertToCharset(value.toString().toStdString());
}
}
break;
default:
break;
}
return QVariant();
}
Qt::ItemFlags TracksModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
Qt::ItemFlags flags = Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
if (_columns[index.column()]->value(0).type() == QVariant::Bool)
flags |= Qt::ItemIsUserCheckable;
if (_columns[index.column()]->isEditable())
flags |= Qt::ItemIsEditable;
return flags;
}
void TracksModel::forceRowDataChanged(int row)
{
const auto begIndex = this->index(row, 0);
const auto endIndex = this->index(row, columnCount(QModelIndex()));
emit dataChanged(begIndex, endIndex);
}
void TracksModel::forceColumnDataChanged(int col)
{
const auto begIndex = this->index(0, col);
const auto endIndex = this->index(rowCount(QModelIndex()), col);
emit dataChanged(begIndex, endIndex);
}
bool TracksModel::setData(const QModelIndex &index, const QVariant &value, int /*role*/)
{
const int trackIndex = trackIndexFromRow(index.row());
if (!isTrackIndexValid(trackIndex) || !isColumnValid(index.column()))
return false;
if (trackIndex == -1) { // all tracks row
for (int i = 0; i != _trackCount; ++i)
_columns[index.column()]->setValue(value, i);
forceColumnDataChanged(index.column());
}
else {
_columns[index.column()]->setValue(value, index.row());
emit dataChanged(index, index);
if (_trackCount > 1) // update 'all tracks' row
forceRowDataChanged(0);
}
return true;
}
QVariant TracksModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
return QCoreApplication::translate("MIDI import track list",
_columns[section]->headerName().toStdString().c_str());
}
else if (orientation == Qt::Vertical && role == Qt::DisplayRole) {
if (_trackCount > 1 && section == 0)
return QCoreApplication::translate("MIDI import operations", "All");
return section + 1;
}
return QVariant();
}
bool TracksModel::isTrackIndexValid(int trackIndex) const
{
return trackIndex >= 0 && trackIndex < _trackCount;
}
bool TracksModel::isColumnValid(int column) const
{
return (column >= 0 && column < (int)_columns.size());
}
} // namespace Ms

55
mscore/importmidi_model.h Normal file
View file

@ -0,0 +1,55 @@
#ifndef IMPORTMIDI_MODEL_H
#define IMPORTMIDI_MODEL_H
#include "importmidi_operations.h"
#include <memory>
namespace Ms {
class TracksModel : public QAbstractTableModel
{
public:
TracksModel();
~TracksModel();
void reset(const MidiOperations::Opers &opers,
const QList<std::string> &lyricsList,
int trackCount);
void clear();
void setTrackShuffleIndex(int trackIndex, int newIndex);
void updateCharset();
const MidiOperations::Opers& trackOpers() const;
int trackCount() const { return _trackCount; }
int trackCountForImport() const;
int frozenRowCount() const;
int frozenColCount() const;
int rowCount(const QModelIndex &/*parent*/) const;
int columnCount(const QModelIndex &/*parent*/) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
private:
bool isTrackIndexValid(int trackIndex) const;
bool isColumnValid(int column) const;
int rowFromTrackIndex(int trackIndex) const;
int trackIndexFromRow(int row) const;
void forceRowDataChanged(int row);
void forceColumnDataChanged(int col);
MidiOperations::Opers _trackOpers;
int _trackCount;
int _frozenColCount;
class Column;
std::vector<std::unique_ptr<Column>> _columns;
};
} // namespace Ms
#endif // IMPORTMIDI_MODEL_H

View file

@ -9,150 +9,90 @@
namespace Ms {
// all enums below should have default indexes like 0, 1, 2...
// text names for enum items are in OperationsModel class
// text names for enum items are in TracksModel class
struct MidiOperation
{
enum class Type {
DO_IMPORT = 0,
namespace MidiOperations {
QUANT_VALUE,
QUANT_HUMAN,
TIME_SIG_NUMERATOR,
TIME_SIG_DENOMINATOR,
DO_LHRH_SEPARATION,
LHRH_METHOD,
LHRH_SPLIT_OCTAVE,
LHRH_SPLIT_NOTE,
USE_DOTS,
SIMPLIFY_DURATIONS,
SHOW_STACCATO,
SEPARATE_VOICES,
ALLOWED_VOICES,
TUPLET_SEARCH,
TUPLET_2,
TUPLET_3,
TUPLET_4,
TUPLET_5,
TUPLET_7,
TUPLET_9,
CHANGE_CLEF,
SPLIT_DRUMS,
SHOW_STAFF_BRACKET,
REMOVE_DRUM_RESTS,
PICKUP_MEASURE,
SWING,
LYRIC_TRACK_INDEX
} type;
QVariant value;
enum class QuantValue {
FROM_PREFERENCES = 0,
N_4,
N_8,
N_16,
N_32,
N_64,
N_128
};
enum class TimeSigNumerator {
_2 = 0,
_3,
_4,
_5,
_6,
_7,
_9,
_12,
_15,
_21
};
enum class TimeSigDenominator {
_2 = 0,
_4,
_8,
_16,
_32
};
enum class AllowedVoices {
V_1 = 0,
V_2,
V_3,
V_4
};
enum class Swing {
NONE = 0,
SWING,
SHUFFLE
};
enum class LHRHMethod {
HAND_WIDTH = 0,
SPECIFIED_PITCH
};
enum class Octave {
C_1 = 0,
C0,
C1,
C2,
C3,
C4,
C5,
C6,
C7,
C8,
C9
};
enum class Note {
C = 0,
Cis,
D,
Dis,
E,
F,
Fis,
G,
Gis,
A,
Ais,
H
};
enum class QuantValue {
FROM_PREFERENCES = 0,
Q_4,
Q_8,
Q_16,
Q_32,
Q_64,
Q_128
};
struct HumanBeatData
{
std::set<ReducedFraction> beatSet;
// to adapt human beats to a different time sig, if necessary
int addedFirstBeats;
int addedLastBeats;
ReducedFraction firstChordTick;
ReducedFraction lastChordTick;
ReducedFraction timeSig;
enum class StaffSplitMethod {
HAND_WIDTH = 0,
SPECIFIED_PITCH
};
enum class StaffSplitOctave {
C_1 = 0,
C0,
C1,
C2,
C3,
C4,
C5,
C6,
C7,
C8,
C9
};
enum class StaffSplitNote {
C = 0,
Cis,
D,
Dis,
E,
F,
Fis,
G,
Gis,
A,
Ais,
H
};
enum class VoiceCount {
V_1 = 0,
V_2,
V_3,
V_4
};
enum class Swing {
NONE = 0,
SWING,
SHUFFLE
};
enum class TimeSigNumerator {
_2 = 0,
_3,
_4,
_5,
_6,
_7,
_9,
_12,
_15,
_21
};
enum class TimeSigDenominator {
_2 = 0,
_4,
_8,
_16,
_32
};
} // namespace MidiOperations
} // namespace Ms
Q_DECLARE_METATYPE(Ms::MidiOperation);
#endif // IMPORTMIDI_OPERATION_H

View file

@ -2,158 +2,67 @@
namespace Ms {
namespace MidiOperations {
bool MidiImportOperations::isValidIndex(int index) const
FileData* Data::data()
{
return index >= 0 && index < operations_.size();
const auto it = _data.find(_currentMidiFile);
if (it != _data.end())
return &it->second;
return nullptr;
}
TrackOperations& MidiImportOperations::defaultOperations(int trackIndex)
const FileData* Data::data() const
{
const auto it = defaultOpersMap.find(trackIndex);
if (it != defaultOpersMap.end())
return it->second;
return defaultOpers;
const auto it = _data.find(_currentMidiFile);
if (it != _data.end())
return &it->second;
return nullptr;
}
const TrackOperations& MidiImportOperations::defaultOperations(int trackIndex) const
void Data::addNewFile(const QString &fileName)
{
const auto it = defaultOpersMap.find(trackIndex);
if (it != defaultOpersMap.end())
return it->second;
return defaultOpers;
_data.insert({fileName, FileData()});
_currentMidiFile = fileName;
}
void MidiImportOperations::appendTrackOperations(const TrackOperations &operations)
const MidiFile* Data::midiFile(const QString &fileName)
{
operations_.push_back(operations);
if (operations_.size() == 1)
currentTrack_ = 0;
const auto it = _data.find(fileName);
if (it != _data.end())
return &it->second.midiFile;
return nullptr;
}
void MidiImportOperations::clear()
void Data::setMidiFileData(const QString &fileName, const MidiFile &midiFile)
{
operations_.clear();
currentTrack_ = -1;
defaultOpers = TrackOperations();
defaultOpersMap.clear();
_data[fileName].midiFile = midiFile;
}
void MidiImportOperations::resetDefaults(const TrackOperations &operations)
void Data::setCurrentMidiFile(const QString &fileName)
{
defaultOpers = operations;
defaultOpersMap.clear();
_currentMidiFile = fileName;
}
void MidiImportOperations::setCurrentTrack(int trackIndex)
void Data::excludeFile(const QString &fileName)
{
if (!isValidIndex(trackIndex))
return;
currentTrack_ = trackIndex;
_data.erase(fileName);
}
void MidiImportOperations::setCurrentMidiFile(const QString &fileName)
bool Data::hasFile(const QString &fileName)
{
currentMidiFile_ = fileName;
return _data.find(fileName) != _data.end();
}
TrackOperations MidiImportOperations::currentTrackOperations() const
int Data::currentTrack() const
{
if (!isValidIndex(currentTrack_))
return defaultOperations(currentTrack_);
return operations_[currentTrack_];
}
TrackOperations MidiImportOperations::trackOperations(int trackIndex) const
{
if (!isValidIndex(trackIndex))
return defaultOperations(trackIndex);
return operations_[trackIndex];
}
QString MidiImportOperations::charset() const
{
return midiData_.charset(currentMidiFile_);
}
void MidiImportOperations::adaptForPercussion(int trackIndex, bool isDrumTrack)
{
// small hack: don't use multiple voices for tuplets in percussion tracks
if (isValidIndex(trackIndex)) {
if (isDrumTrack)
operations_[trackIndex].allowedVoices = MidiOperation::AllowedVoices::V_1;
}
else {
auto &def = defaultOperations(trackIndex);
def.allowedVoices = (isDrumTrack)
? MidiOperation::AllowedVoices::V_1 : TrackOperations().allowedVoices;
}
}
void MidiImportOperations::addTrackLyrics(
const std::multimap<ReducedFraction, std::string> &trackLyrics)
{
midiData_.addTrackLyrics(currentMidiFile_, trackLyrics);
}
const QList<std::multimap<ReducedFraction, std::string> >*
MidiImportOperations::getLyrics()
{
return midiData_.getLyrics(currentMidiFile_);
}
bool MidiImportOperations::isHumanPerformance() const
{
return midiData_.isHumanPerformance(currentMidiFile_);
}
void MidiImportOperations::setHumanPerformance(bool value)
{
midiData_.setHumanPerformance(currentMidiFile_, value);
defaultOperations(currentTrack_).quantize.humanPerformance = value;
}
MidiOperation::QuantValue MidiImportOperations::quantValue() const
{
return midiData_.quantValue(currentMidiFile_);
}
void MidiImportOperations::setQuantValue(MidiOperation::QuantValue value)
{
midiData_.setQuantValue(currentMidiFile_, value);
defaultOperations(currentTrack_).quantize.value = value;
}
bool MidiImportOperations::needToSplit(int trackIndex) const
{
return midiData_.needToSplit(currentMidiFile_, trackIndex);
}
void MidiImportOperations::setNeedToSplit(int trackIndex, bool value)
{
midiData_.setNeedToSplit(currentMidiFile_, trackIndex, value);
defaultOperations(currentTrack_).LHRH.doIt = value;
}
const std::set<ReducedFraction>* MidiImportOperations::getHumanBeats() const
{
return midiData_.getHumanBeats(currentMidiFile_);
}
void MidiImportOperations::setHumanBeats(const HumanBeatData &beatData)
{
return midiData_.setHumanBeats(currentMidiFile_, beatData);
}
ReducedFraction MidiImportOperations::timeSignature() const
{
return midiData_.timeSignature(currentMidiFile_);
}
void MidiImportOperations::setTimeSignature(const ReducedFraction &value)
{
return midiData_.setTimeSignature(currentMidiFile_, value);
Q_ASSERT_X(_currentTrack >= 0,
"Data::currentTrack", "Invalid current track index");
return _currentTrack;
}
} // namespace MidiOperations
} // namespace Ms

View file

@ -2,11 +2,16 @@
#define IMPORTMIDI_OPERATIONS_H
#include "importmidi_operation.h"
#include "importmidi_data.h"
#include "midi/midifile.h"
#include "importmidi_inner.h"
namespace Ms {
class ReducedFraction;
namespace MidiOperations {
// operation types are in importmidi_operation.h
// to add an operation one need to add code also to:
@ -15,147 +20,176 @@ namespace Ms {
// - importmidi_trmodel.cpp (2 places),
// and - other importmidi files where algorithm requires it
struct SearchTuplets
{
bool doSearch = true;
bool duplets = false;
bool triplets = true;
bool quadruplets = true;
bool quintuplets = true;
bool septuplets = true;
bool nonuplets = true;
};
struct Quantization
{
MidiOperation::QuantValue value = MidiOperation::QuantValue::FROM_PREFERENCES;
bool humanPerformance = false;
};
struct LHRHSeparation
{
bool doIt = false;
MidiOperation::LHRHMethod method = MidiOperation::LHRHMethod::HAND_WIDTH;
MidiOperation::Octave splitPitchOctave = MidiOperation::Octave::C4;
MidiOperation::Note splitPitchNote = MidiOperation::Note::E;
};
struct SplitDrums
{
bool doSplit = false;
bool showStaffBracket = true;
};
struct MidiTimeSig
{
MidiOperation::TimeSigNumerator numerator = MidiOperation::TimeSigNumerator::_4;
MidiOperation::TimeSigDenominator denominator = MidiOperation::TimeSigDenominator::_4;
};
// bool and enum-like elementary operations (itself and inside structs) are allowed
struct TrackOperations
{
bool canRedefineDefaultsLater = true; // can try to adapt defaults to the imported score
int reorderedIndex = 0;
bool doImport = true;
Quantization quantize;
bool useDots = true;
bool simplifyDurations = true;
bool showStaccato = true;
bool separateVoices = true;
LHRHSeparation LHRH;
SearchTuplets tuplets;
MidiOperation::AllowedVoices allowedVoices = MidiOperation::AllowedVoices::V_4;
bool changeClef = true;
MidiOperation::Swing swing = MidiOperation::Swing::NONE;
SplitDrums splitDrums;
bool removeDrumRests = true;
bool pickupMeasure = true;
int lyricTrackIndex = -1; // empty lyric
MidiTimeSig timeSig;
};
struct TrackMeta
{
std::string staffName; // will be converted to unicode later
QString instrumentName;
bool isDrumTrack;
int initLyricTrackIndex;
};
struct TrackData
{
TrackMeta meta;
TrackOperations opers;
};
struct DefinedTrackOperations
{
QSet<int> undefinedOpers;
bool isDrumTrack;
bool allTracksSelected;
TrackOperations opers;
};
class ReducedFraction;
class MidiImportOperations
template<typename T>
class TrackOp
{
public:
void appendTrackOperations(const TrackOperations& operations);
void clear();
void resetDefaults(const TrackOperations& operations);
void setCurrentTrack(int trackIndex);
void setCurrentMidiFile(const QString &fileName);
int currentTrack() const { return currentTrack_; }
TrackOperations defaultOperations() const { return defaultOpers; }
TrackOperations currentTrackOperations() const;
TrackOperations trackOperations(int trackIndex) const;
int count() const { return operations_.size(); }
MidiData& midiData() { return midiData_; }
QString charset() const;
void adaptForPercussion(int trackIndex, bool isDrumTrack);
TrackOp(T defaultValue)
: _operation{{-1, defaultValue}}
{}
// lyrics
void addTrackLyrics(const std::multimap<ReducedFraction, std::string> &trackLyrics);
const QList<std::multimap<ReducedFraction, std::string> > *getLyrics();
TrackOp<T>& operator=(const TrackOp<T> &op)
{
if (this == &op)
return *this;
for (const auto &pair: op._operation) {
if (pair.second != value(pair.first) && pair.second != defaultValue()) {
_operation[pair.first] = pair.second;
}
}
return *this;
}
// human performance: is MIDI unaligned
bool isHumanPerformance() const;
void setHumanPerformance(bool value);
const std::set<ReducedFraction>* getHumanBeats() const;
void setHumanBeats(const HumanBeatData &beatData);
// time sig can be zero (not specified) if, when opened,
// MIDI file was not detected as human-performed
ReducedFraction timeSignature() const;
void setTimeSignature(const ReducedFraction &value);
T value(int trackIndex) const
{
const auto it = _operation.find(trackIndex);
if (it == _operation.end())
return _operation.find(-1)->second;
return it->second;
}
// quantization
MidiOperation::QuantValue quantValue() const;
void setQuantValue(MidiOperation::QuantValue value);
void setValue(int trackIndex, T value)
{
Q_ASSERT_X(trackIndex >= 0, "TrackOperation", "Invalid track index");
// left/right hand split
bool needToSplit(int trackIndex) const;
void setNeedToSplit(int trackIndex, bool value);
if (value != defaultValue())
_operation[trackIndex] = value;
}
T defaultValue() const
{
return _operation.find(-1)->second;
}
void setDefaultValue(T value)
{
_operation[-1] = value;
}
private:
TrackOperations& defaultOperations(int trackIndex);
const TrackOperations& defaultOperations(int trackIndex) const;
QList<TrackOperations> operations_;
std::map<int, TrackOperations> defaultOpersMap; // <track index, operations>
TrackOperations defaultOpers;
int currentTrack_ = -1;
QString currentMidiFile_;
MidiData midiData_;
bool isValidIndex(int index) const;
// <track index, operation value>
// if track index == -1 then it's default value (for all tracks)
std::map<int, T> _operation;
};
// values that can be changed
struct Opers
{
// data that cannot be changed by the user
TrackOp<std::string> staffName = std::string(); // will be converted to unicode later
TrackOp<QString> instrumentName = QString();
TrackOp<bool> isDrumTrack = false;
// operations for all tracks
bool isHumanPerformance = false;
bool searchPickupMeasure = true;
TimeSigNumerator timeSigNumerator = TimeSigNumerator::_4;
TimeSigDenominator timeSigDenominator = TimeSigDenominator::_4;
// operations for individual tracks
TrackOp<int> trackIndexAfterShuffle = 0;
TrackOp<bool> doImport = true;
TrackOp<QuantValue> quantValue = QuantValue::FROM_PREFERENCES;
TrackOp<bool> searchTuplets = true;
TrackOp<bool> search2plets = false;
TrackOp<bool> search3plets = true;
TrackOp<bool> search4plets = true;
TrackOp<bool> search5plets = true;
TrackOp<bool> search7plets = true;
TrackOp<bool> search9plets = true;
TrackOp<bool> useDots = true;
TrackOp<bool> simplifyDurations = true; // for drum tracks - remove rests and ties
TrackOp<bool> showStaccato = true;
TrackOp<bool> doStaffSplit = false; // for drum tracks - split by voices
TrackOp<StaffSplitMethod> staffSplitMethod = StaffSplitMethod::HAND_WIDTH;
TrackOp<StaffSplitOctave> staffSplitOctave = StaffSplitOctave::C4;
TrackOp<StaffSplitNote> staffSplitNote = StaffSplitNote::E;
TrackOp<VoiceCount> maxVoiceCount = VoiceCount::V_4;
TrackOp<bool> changeClef = true;
TrackOp<Swing> swing = Swing::NONE;
TrackOp<bool> removeDrumRests = true;
TrackOp<int> lyricTrackIndex = -1; // empty lyric
};
struct HumanBeatData
{
std::set<ReducedFraction> beatSet;
// to adapt human beats to a different time sig, if necessary
int addedFirstBeats;
int addedLastBeats;
ReducedFraction firstChordTick;
ReducedFraction lastChordTick;
ReducedFraction timeSig;
};
struct FileData
{
MidiFile midiFile;
bool isNewlyOpened = true;
bool canRedefineDefaultsLater = true;
QByteArray HHeaderData;
QByteArray VHeaderData;
int selectedRow = 0;
int trackCount = 0;
MidiOperations::Opers trackOpers;
QString charset = MidiCharset::defaultCharset();
// after the user apply MIDI import operations
// this value should be set to false
// tracks of <tick, lyric fragment> from karaoke files
// QList of lyric tracks - there can be multiple lyric tracks,
// lyric track count != MIDI track count in general
QList<std::multimap<ReducedFraction, std::string>> lyricTracks;
HumanBeatData humanBeatData;
};
class Data
{
public:
FileData* data();
const FileData* data() const;
void addNewFile(const QString &fileName);
int currentTrack() const;
void setMidiFileData(const QString &fileName, const MidiFile &midiFile);
void setCurrentMidiFile(const QString &fileName);
void excludeFile(const QString &fileName);
bool hasFile(const QString &fileName);
const MidiFile* midiFile(const QString &fileName);
private:
friend class CurrentTrackSetter;
QString _currentMidiFile;
int _currentTrack = -1;
std::map<QString, FileData> _data; // <file name, tracks data>
};
// scoped setter of current track
class CurrentTrackSetter
{
public:
CurrentTrackSetter(Data &opers, int track)
: _opers(opers)
{
_opers._currentTrack = track;
}
~CurrentTrackSetter()
{
_opers._currentTrack = -1;
}
private:
Data &_opers;
// disallow heap allocation - for stack-only usage
void* operator new(size_t); // standard new
void* operator new(size_t, void*); // placement new
void* operator new[](size_t); // array new
void* operator new[](size_t, void*); // placement array new
};
} // namespace MidiOperations
} // namespace Ms
Q_DECLARE_METATYPE(Ms::TrackData);
#endif // IMPORTMIDI_OPERATIONS_H

View file

@ -1,772 +0,0 @@
#include "importmidi_opmodel.h"
#include "importmidi_operations.h"
#include "preferences.h"
#include "libmscore/mscore.h"
namespace Ms {
extern Preferences preferences;
struct Node {
QString name;
MidiOperation oper;
QStringList values;
bool visible = true;
Node *parent = nullptr;
std::vector<std::unique_ptr<Node> > children;
void setParent(Node *par)
{
parent = par;
par->children.push_back(std::unique_ptr<Node>(this));
}
};
struct Controller {
Node *LHRHdoIt = nullptr;
Node *LHRHMethod = nullptr;
Node *LHRHPitchOctave = nullptr;
Node *LHRHPitchNote = nullptr;
Node *quantValue = nullptr;
Node *quantHuman = nullptr;
Node *searchTuplets = nullptr;
Node *duplets = nullptr;
Node *triplets = nullptr;
Node *quadruplets = nullptr;
Node *quintuplets = nullptr;
Node *septuplets = nullptr;
Node *nonuplets = nullptr;
Node *allowedVoices = nullptr;
Node *separateVoices = nullptr;
Node *splitDrums = nullptr;
Node *showStaffBracket = nullptr;
Node *pickupMeasure = nullptr;
Node *clef = nullptr;
Node *removeDrumRests = nullptr;
Node *simplifyDurations = nullptr;
Node *showStaccato = nullptr;
Node *timeSig = nullptr;
bool isDrumTrack = false;
bool allTracksSelected = true;
bool timeSigVisible = false;
bool updateNodeDependencies(Node *node, bool forceUpdate);
};
OperationsModel::OperationsModel()
: root(std::unique_ptr<Node>(new Node()))
, controller(std::unique_ptr<Controller>(new Controller()))
, updateQuantTimer(new QTimer)
{
connect(updateQuantTimer, SIGNAL(timeout()), this, SLOT(updateQuantValue()));
updateQuantTimer->start(100);
beginResetModel();
// - initialize opeations with their default values
// - string lists below should match Operation enum values
Node *quantValue = new Node;
quantValue->name = QCoreApplication::translate("MIDI import operations", "Max quantization value");
quantValue->oper.type = MidiOperation::Type::QUANT_VALUE;
quantValue->oper.value = (int)TrackOperations().quantize.value;
quantValue->values.push_back(QCoreApplication::translate("MIDI import operations", "Value from preferences"));
quantValue->values.push_back(QCoreApplication::translate("MIDI import operations", "Quarter"));
quantValue->values.push_back(QCoreApplication::translate("MIDI import operations", "Eighth"));
quantValue->values.push_back(QCoreApplication::translate("MIDI import operations", "16th"));
quantValue->values.push_back(QCoreApplication::translate("MIDI import operations", "32nd"));
quantValue->values.push_back(QCoreApplication::translate("MIDI import operations", "64th"));
quantValue->values.push_back(QCoreApplication::translate("MIDI import operations", "128th"));
quantValue->setParent(root.get());
controller->quantValue = quantValue;
Node *humanPerformance = new Node;
humanPerformance->name = QCoreApplication::translate("MIDI import operations", "Human performance");
humanPerformance->oper.type = MidiOperation::Type::QUANT_HUMAN;
humanPerformance->oper.value = Quantization().humanPerformance;
humanPerformance->setParent(quantValue);
controller->quantHuman = humanPerformance;
Node *timeSigNumerator = new Node;
timeSigNumerator->name = QCoreApplication::translate("MIDI import operations", "Time signature");
timeSigNumerator->oper.type = MidiOperation::Type::TIME_SIG_NUMERATOR;
timeSigNumerator->oper.value = (int)MidiTimeSig().numerator;
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "2"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "3"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "4"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "5"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "6"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "7"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "9"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "12"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "15"));
timeSigNumerator->values.push_back(QCoreApplication::translate("MIDI import operations", "21"));
timeSigNumerator->setParent(root.get());
controller->timeSig = timeSigNumerator;
Node *timeSigDenominator = new Node;
timeSigDenominator->name = QCoreApplication::translate("MIDI import operations", "/");
timeSigDenominator->oper.type = MidiOperation::Type::TIME_SIG_DENOMINATOR;
timeSigDenominator->oper.value = (int)MidiTimeSig().denominator;
timeSigDenominator->values.push_back(QCoreApplication::translate("MIDI import operations", "2"));
timeSigDenominator->values.push_back(QCoreApplication::translate("MIDI import operations", "4"));
timeSigDenominator->values.push_back(QCoreApplication::translate("MIDI import operations", "8"));
timeSigDenominator->values.push_back(QCoreApplication::translate("MIDI import operations", "16"));
timeSigDenominator->values.push_back(QCoreApplication::translate("MIDI import operations", "32"));
timeSigDenominator->setParent(timeSigNumerator);
Node *useDots = new Node;
useDots->name = QCoreApplication::translate("MIDI import operations", "Use dots");
useDots->oper.type = MidiOperation::Type::USE_DOTS;
useDots->oper.value = TrackOperations().useDots;
useDots->setParent(root.get());
Node *simplifyDurations = new Node;
simplifyDurations->name = QCoreApplication::translate("MIDI import operations", "Simplify durations");
simplifyDurations->oper.type = MidiOperation::Type::SIMPLIFY_DURATIONS;
simplifyDurations->oper.value = TrackOperations().simplifyDurations;
simplifyDurations->setParent(root.get());
controller->simplifyDurations = simplifyDurations;
Node *showStaccato = new Node;
showStaccato->name = QCoreApplication::translate("MIDI import operations", "Show staccato");
showStaccato->oper.type = MidiOperation::Type::SHOW_STACCATO;
showStaccato->oper.value = TrackOperations().showStaccato;
showStaccato->setParent(simplifyDurations);
controller->showStaccato = showStaccato;
Node *allowedVoices = new Node;
allowedVoices->name = QCoreApplication::translate("MIDI import operations", "Max allowed voices");
allowedVoices->oper.type = MidiOperation::Type::ALLOWED_VOICES;
allowedVoices->oper.value = (int)TrackOperations().allowedVoices;
allowedVoices->values.push_back(QCoreApplication::translate("MIDI import operations", "1"));
allowedVoices->values.push_back(QCoreApplication::translate("MIDI import operations", "2"));
allowedVoices->values.push_back(QCoreApplication::translate("MIDI import operations", "3"));
allowedVoices->values.push_back(QCoreApplication::translate("MIDI import operations", "4"));
allowedVoices->setParent(root.get());
controller->allowedVoices = allowedVoices;
Node *separateVoices = new Node;
separateVoices->name = QCoreApplication::translate("MIDI import operations", "Separate voices to remove ties");
separateVoices->oper.type = MidiOperation::Type::SEPARATE_VOICES;
separateVoices->oper.value = TrackOperations().separateVoices;
separateVoices->setParent(allowedVoices);
controller->separateVoices = separateVoices;
// ------------- staff separation --------------
Node *doLHRH = new Node;
doLHRH->name = QCoreApplication::translate("MIDI import operations", "Staff separation");
doLHRH->oper.type = MidiOperation::Type::DO_LHRH_SEPARATION;
doLHRH->oper.value = LHRHSeparation().doIt;
doLHRH->setParent(root.get());
controller->LHRHdoIt = doLHRH;
Node *LHRHMethod = new Node;
LHRHMethod->name = QCoreApplication::translate("MIDI import operations", "Separation method");
LHRHMethod->oper.type = MidiOperation::Type::LHRH_METHOD;
LHRHMethod->oper.value = (int)LHRHSeparation().method;
LHRHMethod->values.push_back(QCoreApplication::translate("MIDI import operations", "Hand width"));
LHRHMethod->values.push_back(QCoreApplication::translate("MIDI import operations", "Fixed pitch"));
LHRHMethod->setParent(doLHRH);
controller->LHRHMethod = LHRHMethod;
Node *LHRHPitchOctave = new Node;
LHRHPitchOctave->name = QCoreApplication::translate("MIDI import operations", "Split pitch octave");
LHRHPitchOctave->oper.type = MidiOperation::Type::LHRH_SPLIT_OCTAVE;
LHRHPitchOctave->oper.value = (int)LHRHSeparation().splitPitchOctave;
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C-1"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C0"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C1"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C2"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C3"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C4"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C5"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C6"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C7"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C8"));
LHRHPitchOctave->values.push_back(QCoreApplication::translate("MIDI import operations", "C9"));
LHRHPitchOctave->setParent(LHRHMethod);
controller->LHRHPitchOctave = LHRHPitchOctave;
Node *LHRHPitchNote = new Node;
LHRHPitchNote->name = QCoreApplication::translate("MIDI import operations", "Split pitch note");
LHRHPitchNote->oper.type = MidiOperation::Type::LHRH_SPLIT_NOTE;
LHRHPitchNote->oper.value = (int)LHRHSeparation().splitPitchNote;
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "C"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "C#"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "D"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "D#"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "E"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "F"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "F#"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "G"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "G#"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "A"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "A#"));
LHRHPitchNote->values.push_back(QCoreApplication::translate("MIDI import operations", "B"));
LHRHPitchNote->setParent(LHRHMethod);
controller->LHRHPitchNote = LHRHPitchNote;
// ------------- tuplets --------------
Node *searchTuplets = new Node;
searchTuplets->name = QCoreApplication::translate("MIDI import operations", "Search tuplets");
searchTuplets->oper.type = MidiOperation::Type::TUPLET_SEARCH;
searchTuplets->oper.value = TrackOperations().tuplets.doSearch;
searchTuplets->setParent(root.get());
controller->searchTuplets = searchTuplets;
Node *duplets = new Node;
duplets->name = QCoreApplication::translate("MIDI import operations", "Duplets (2)");
duplets->oper.type = MidiOperation::Type::TUPLET_2;
duplets->oper.value = TrackOperations().tuplets.duplets;
duplets->setParent(searchTuplets);
controller->duplets = duplets;
Node *triplets = new Node;
triplets->name = QCoreApplication::translate("MIDI import operations", "Triplets (3)");
triplets->oper.type = MidiOperation::Type::TUPLET_3;
triplets->oper.value = TrackOperations().tuplets.triplets;
triplets->setParent(searchTuplets);
controller->triplets = triplets;
Node *quadruplets = new Node;
quadruplets->name = QCoreApplication::translate("MIDI import operations", "Quadruplets (4)");
quadruplets->oper.type = MidiOperation::Type::TUPLET_4;
quadruplets->oper.value = TrackOperations().tuplets.quadruplets;
quadruplets->setParent(searchTuplets);
controller->quadruplets = quadruplets;
Node *quintuplets = new Node;
quintuplets->name = QCoreApplication::translate("MIDI import operations", "Quintuplets (5)");
quintuplets->oper.type = MidiOperation::Type::TUPLET_5;
quintuplets->oper.value = TrackOperations().tuplets.quintuplets;
quintuplets->setParent(searchTuplets);
controller->quintuplets = quintuplets;
Node *septuplets = new Node;
septuplets->name = QCoreApplication::translate("MIDI import operations", "Septuplets (7)");
septuplets->oper.type = MidiOperation::Type::TUPLET_7;
septuplets->oper.value = TrackOperations().tuplets.septuplets;
septuplets->setParent(searchTuplets);
controller->septuplets = septuplets;
Node *nonuplets = new Node;
nonuplets->name = QCoreApplication::translate("MIDI import operations", "Nonuplets (9)");
nonuplets->oper.type = MidiOperation::Type::TUPLET_9;
nonuplets->oper.value = TrackOperations().tuplets.nonuplets;
nonuplets->setParent(searchTuplets);
controller->nonuplets = nonuplets;
// ------------------------------------
Node *pickupMeasure = new Node;
pickupMeasure->name = QCoreApplication::translate("MIDI import operations", "Recognize pickup measure");
pickupMeasure->oper.type = MidiOperation::Type::PICKUP_MEASURE;
pickupMeasure->oper.value = TrackOperations().pickupMeasure;
pickupMeasure->setParent(root.get());
controller->pickupMeasure = pickupMeasure;
Node *swing = new Node;
swing->name = QCoreApplication::translate("MIDI import operations", "Detect swing");
swing->oper.type = MidiOperation::Type::SWING;
swing->oper.value = (int)TrackOperations().swing;
swing->values.push_back(QCoreApplication::translate("MIDI import operations", "None (1:1)"));
swing->values.push_back(QCoreApplication::translate("MIDI import operations", "Swing (2:1)"));
swing->values.push_back(QCoreApplication::translate("MIDI import operations", "Shuffle (3:1)"));
swing->setParent(root.get());
Node *changeClef = new Node;
changeClef->name = QCoreApplication::translate("MIDI import operations", "Allow clef changes within a staff");
changeClef->oper.type = MidiOperation::Type::CHANGE_CLEF;
changeClef->oper.value = TrackOperations().changeClef;
changeClef->setParent(root.get());
controller->clef = changeClef;
Node *removeDrumRests = new Node;
removeDrumRests->name = QCoreApplication::translate("MIDI import operations", "Remove rests and ties between notes");
removeDrumRests->oper.type = MidiOperation::Type::REMOVE_DRUM_RESTS;
removeDrumRests->oper.value = TrackOperations().removeDrumRests;
removeDrumRests->setParent(root.get());
controller->removeDrumRests = removeDrumRests;
Node *splitDrums = new Node;
splitDrums->name = QCoreApplication::translate("MIDI import operations", "Split drum set");
splitDrums->oper.type = MidiOperation::Type::SPLIT_DRUMS;
splitDrums->oper.value = TrackOperations().splitDrums.doSplit;;
splitDrums->setParent(root.get());
controller->splitDrums = splitDrums;
Node *showStaffBracket = new Node;
showStaffBracket->name = QCoreApplication::translate("MIDI import operations", "Show staff bracket");
showStaffBracket->oper.type = MidiOperation::Type::SHOW_STAFF_BRACKET;
showStaffBracket->oper.value = TrackOperations().splitDrums.showStaffBracket;
showStaffBracket->setParent(splitDrums);
controller->showStaffBracket = showStaffBracket;
//--------------------------------------------------------------------
connect(this,
SIGNAL(dataChanged(QModelIndex,QModelIndex)),
SLOT(onDataChanged(QModelIndex)));
controller->updateNodeDependencies(nullptr, true);
endResetModel();
}
OperationsModel::~OperationsModel()
{
}
void OperationsModel::setTimeSigVisibility(bool value)
{
controller->timeSigVisible = value;
}
QModelIndex OperationsModel::index(int row, int column, const QModelIndex &parent) const
{
if (!root || row < 0 || column < 0 || column >= OperationCol::OCOL_COUNT)
return QModelIndex();
const Node *parentNode = nodeFromIndex(parent);
if (!parentNode)
return QModelIndex();
if (parentNode->children.empty() || row >= (int)parentNode->children.size())
return QModelIndex();
// find new row in connection with invisible items
int shift = 0;
for (int i = 0; i <= row + shift; ++i) {
if (i >= (int)parentNode->children.size())
return QModelIndex();
if (!parentNode->children.at(i)->visible)
++shift;
}
Node *childNode = parentNode->children.at(row + shift).get();
if (!childNode || !childNode->visible)
return QModelIndex();
return createIndex(row, column, childNode);
}
QModelIndex OperationsModel::parent(const QModelIndex &child) const
{
const Node *node = nodeFromIndex(child);
if (!node)
return QModelIndex();
Node *parentNode = node->parent;
if (!parentNode)
return QModelIndex();
const Node *grandparentNode = parentNode->parent;
if (!grandparentNode)
return QModelIndex();
const auto &children = grandparentNode->children;
const auto iter = std::find_if(children.begin(), children.end(),
[parentNode](const std::unique_ptr<Node> &el){ return el.get() == parentNode; });
const int row = (iter == children.end()) ? -1 : iter - children.begin();
return createIndex(row, 0, parentNode);
}
int OperationsModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() >= OperationCol::OCOL_COUNT)
return 0;
const Node *parentNode = nodeFromIndex(parent);
if (!parentNode)
return 0;
// take only visible nodes into account
size_t counter = 0;
for (const auto &p: parentNode->children)
if (p->visible)
++counter;
return counter;
}
int OperationsModel::columnCount(const QModelIndex &/*parent*/) const
{
return OperationCol::OCOL_COUNT;
}
// 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
{
const Node *node = nodeFromIndex(index);
if (!node)
return QVariant();
switch (role) {
case DataRole:
if (node->values.empty() && !node->values.empty()) // checkbox
return node->oper.value.toBool();
else
return node->oper.value.toInt();
break;
case Qt::DisplayRole:
switch (index.column()) {
case OperationCol::OPER_NAME:
return node->name;
case OperationCol::VALUE:
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
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
break;
default:
break;
}
break;
case Qt::EditRole:
if (index.column() == OperationCol::VALUE && !node->values.empty())
return node->values;
break;
case Qt::CheckStateRole:
if (index.column() == OperationCol::VALUE && node->values.empty()) {
if (!node->oper.value.isValid())
return Qt::PartiallyChecked;
return (node->oper.value.toBool())
? Qt::Checked : Qt::Unchecked;
}
break;
case Qt::SizeHintRole:
{
QSize sz;
sz.setHeight(22);
return sz;
}
case OperationTypeRole:
return (int)node->oper.type;
default:
break;
}
return QVariant();
}
QVariant OperationsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section) {
case OperationCol::OPER_NAME:
return QCoreApplication::translate("MIDI import operations",
"Selected track [%1] operations").arg(trackLabel);
case OperationCol::VALUE:
return QCoreApplication::translate("MIDI import operations", "Value");
default:
break;
}
}
return QVariant();
}
Qt::ItemFlags OperationsModel::flags(const QModelIndex &index) const
{
const Node *node = nodeFromIndex(index);
if (!node)
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
flags |= Qt::ItemIsUserCheckable;
else // node has list of values
flags |= Qt::ItemIsEditable;
}
return flags;
}
bool OperationsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
Node *node = nodeFromIndex(index);
if (!node)
return false;
bool result = false;
if (index.column() == OperationCol::VALUE) {
switch (role) {
case Qt::CheckStateRole:
node->oper.value = value.toBool();
result = true;
break;
case Qt::EditRole:
// set enum value from value == list index
node->oper.value = value.toInt();
result = true;
break;
default:
break;
}
}
if (result)
emit dataChanged(index, index);
return result;
}
void setNodeOperations(Node *node, const DefinedTrackOperations &opers)
{
if (opers.undefinedOpers.contains((int)node->oper.type))
node->oper.value = QVariant();
else {
switch (node->oper.type) {
case MidiOperation::Type::DO_IMPORT:
case MidiOperation::Type::LYRIC_TRACK_INDEX:
break;
case MidiOperation::Type::QUANT_VALUE:
node->oper.value = (int)opers.opers.quantize.value; break;
case MidiOperation::Type::QUANT_HUMAN:
node->oper.value = opers.opers.quantize.humanPerformance; break;
case MidiOperation::Type::TIME_SIG_NUMERATOR:
node->oper.value = (int)opers.opers.timeSig.numerator; break;
case MidiOperation::Type::TIME_SIG_DENOMINATOR:
node->oper.value = (int)opers.opers.timeSig.denominator; break;
case MidiOperation::Type::DO_LHRH_SEPARATION:
node->oper.value = opers.opers.LHRH.doIt; break;
case MidiOperation::Type::LHRH_METHOD:
node->oper.value = (int)opers.opers.LHRH.method; break;
case MidiOperation::Type::LHRH_SPLIT_OCTAVE:
node->oper.value = (int)opers.opers.LHRH.splitPitchOctave; break;
case MidiOperation::Type::LHRH_SPLIT_NOTE:
node->oper.value = (int)opers.opers.LHRH.splitPitchNote; break;
case MidiOperation::Type::USE_DOTS:
node->oper.value = opers.opers.useDots; break;
case MidiOperation::Type::SIMPLIFY_DURATIONS:
node->oper.value = opers.opers.simplifyDurations; break;
case MidiOperation::Type::SHOW_STACCATO:
node->oper.value = opers.opers.showStaccato; break;
case MidiOperation::Type::SEPARATE_VOICES:
node->oper.value = opers.opers.separateVoices; break;
case MidiOperation::Type::SWING:
node->oper.value = (int)opers.opers.swing; break;
case MidiOperation::Type::ALLOWED_VOICES:
node->oper.value = (int)opers.opers.allowedVoices; break;
case MidiOperation::Type::TUPLET_SEARCH:
node->oper.value = opers.opers.tuplets.doSearch; break;
case MidiOperation::Type::TUPLET_2:
node->oper.value = opers.opers.tuplets.duplets; break;
case MidiOperation::Type::TUPLET_3:
node->oper.value = opers.opers.tuplets.triplets; break;
case MidiOperation::Type::TUPLET_4:
node->oper.value = opers.opers.tuplets.quadruplets; break;
case MidiOperation::Type::TUPLET_5:
node->oper.value = opers.opers.tuplets.quintuplets; break;
case MidiOperation::Type::TUPLET_7:
node->oper.value = opers.opers.tuplets.septuplets; break;
case MidiOperation::Type::TUPLET_9:
node->oper.value = opers.opers.tuplets.nonuplets; break;
case MidiOperation::Type::CHANGE_CLEF:
node->oper.value = opers.opers.changeClef; break;
case MidiOperation::Type::PICKUP_MEASURE:
node->oper.value = opers.opers.pickupMeasure; break;
case MidiOperation::Type::SPLIT_DRUMS:
node->oper.value = opers.opers.splitDrums.doSplit; break;
case MidiOperation::Type::SHOW_STAFF_BRACKET:
node->oper.value = opers.opers.splitDrums.showStaffBracket; break;
case MidiOperation::Type::REMOVE_DRUM_RESTS:
node->oper.value = opers.opers.removeDrumRests; break;
}
}
for (const auto &nodePtr: node->children)
setNodeOperations(nodePtr.get(), opers);
}
void OperationsModel::setTrackData(const QString &trackLabel,
const DefinedTrackOperations &opers)
{
this->trackLabel = trackLabel;
controller->isDrumTrack = opers.isDrumTrack;
controller->allTracksSelected = opers.allTracksSelected;
// set new operations values
beginResetModel();
for (const auto &nodePtr: root->children)
setNodeOperations(nodePtr.get(), opers);
controller->updateNodeDependencies(nullptr, true);
endResetModel();
}
void OperationsModel::onDataChanged(const QModelIndex &index)
{
Node *node = nodeFromIndex(index);
if (!node)
return;
if (controller->updateNodeDependencies(node, false))
layoutChanged();
}
void OperationsModel::updateQuantValue()
{
const auto newPrefQuantValue = ReducedFraction::fromTicks(preferences.shortestNote);
if (newPrefQuantValue == prefQuantValue)
return;
prefQuantValue = newPrefQuantValue;
const auto division = ReducedFraction::fromTicks(MScore::division);
QString value;
if (prefQuantValue == division)
value += "Quarter";
else if (prefQuantValue == division / 2)
value += "Eighth";
else if (prefQuantValue == division / 4)
value += "16th";
else if (prefQuantValue == division / 8)
value += "32nd";
else if (prefQuantValue == division / 16)
value += "64th";
else if (prefQuantValue == division / 32)
value += "128th";
else
Q_ASSERT_X(false, "OperationsModel::updateQuantValue", "Unknown quantization value");
controller->quantValue->values[0] = QCoreApplication::translate(
"MIDI import operations", "Value from preferences (%1)").arg(value);
emit dataChanged(QModelIndex(), QModelIndex());
}
Node* OperationsModel::nodeFromIndex(const QModelIndex &index) const
{
if (index.isValid())
return static_cast<Node *>(index.internalPointer());
else
return root.get();
}
// Different controller actions, i.e. conditional visibility of node
bool Controller::updateNodeDependencies(Node *node, bool forceUpdate)
{
bool result = false;
if (!node && !forceUpdate)
return result;
if (LHRHMethod && (forceUpdate || node == LHRHMethod)) {
const auto value = (MidiOperation::LHRHMethod)LHRHMethod->oper.value.toInt();
switch (value) {
case MidiOperation::LHRHMethod::HAND_WIDTH:
if (LHRHPitchOctave)
LHRHPitchOctave->visible = false;
if (LHRHPitchNote)
LHRHPitchNote->visible = false;
result = true;
break;
case MidiOperation::LHRHMethod::SPECIFIED_PITCH:
if (LHRHPitchOctave)
LHRHPitchOctave->visible = true;
if (LHRHPitchNote)
LHRHPitchNote->visible = true;
result = true;
break;
}
}
if (LHRHdoIt && (forceUpdate || node == LHRHdoIt)) {
const auto value = LHRHdoIt->oper.value.toBool();
if (LHRHMethod)
LHRHMethod->visible = value;
result = true;
}
if (simplifyDurations && (forceUpdate || node == simplifyDurations)) {
const auto value = simplifyDurations->oper.value.toBool();
if (showStaccato)
showStaccato->visible = value;
result = true;
}
if (allowedVoices && (forceUpdate || node == allowedVoices)) {
const auto value = (MidiOperation::AllowedVoices)allowedVoices->oper.value.toInt();
switch (value) {
case MidiOperation::AllowedVoices::V_1:
if (separateVoices)
separateVoices->visible = false;
result = true;
break;
case MidiOperation::AllowedVoices::V_2:
case MidiOperation::AllowedVoices::V_3:
case MidiOperation::AllowedVoices::V_4:
if (separateVoices)
separateVoices->visible = true;
result = true;
break;
}
}
if (searchTuplets && (forceUpdate || node == searchTuplets)) {
const auto value = searchTuplets->oper.value.toBool();
if (duplets)
duplets->visible = value;
if (triplets)
triplets->visible = value;
if (quadruplets)
quadruplets->visible = value;
if (quintuplets)
quintuplets->visible = value;
if (septuplets)
septuplets->visible = value;
if (nonuplets)
nonuplets->visible = value;
result = true;
}
if (splitDrums && (forceUpdate || node == splitDrums)) {
const auto value = splitDrums->oper.value.toBool();
if (showStaffBracket)
showStaffBracket->visible = value;
result = true;
}
if (forceUpdate) {
if (LHRHdoIt)
LHRHdoIt->visible = !isDrumTrack;
if (splitDrums)
splitDrums->visible = allTracksSelected || isDrumTrack;
if (removeDrumRests)
removeDrumRests->visible = allTracksSelected || isDrumTrack;
if (allowedVoices)
allowedVoices->visible = !isDrumTrack;
if (separateVoices)
separateVoices->visible = !isDrumTrack;
if (clef)
clef->visible = !isDrumTrack;
if (pickupMeasure)
pickupMeasure->visible = allTracksSelected;
if (quantHuman)
quantHuman->visible = allTracksSelected;
if (timeSig)
timeSig->visible = allTracksSelected && timeSigVisible;
result = true;
}
return result;
}
} // namespace Ms

View file

@ -1,65 +0,0 @@
#ifndef IMPORTMIDI_OPMODEL_H
#define IMPORTMIDI_OPMODEL_H
#include "importmidi_operation.h"
#include "importmidi_fraction.h"
#include <memory>
namespace Ms {
enum {
OperationTypeRole = 100,
DataRole
};
struct Node;
struct Controller;
struct TrackOperations;
struct DefinedTrackOperations;
enum OperationCol {
OPER_NAME,
VALUE,
OCOL_COUNT
};
class OperationsModel : public QAbstractItemModel
{
Q_OBJECT
public:
OperationsModel();
~OperationsModel();
void setTimeSigVisibility(bool value);
void setTrackData(const QString &trackLabel, const Ms::DefinedTrackOperations &opers);
QModelIndex index(int row, int column, const QModelIndex &parent) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
private slots:
void onDataChanged(const QModelIndex &index);
void updateQuantValue();
private:
std::unique_ptr<Node> root;
std::unique_ptr<Controller> controller;
QString trackLabel;
QTimer *updateQuantTimer;
ReducedFraction prefQuantValue;
Node* nodeFromIndex(const QModelIndex &index) const;
};
} // namespace Ms
#endif // IMPORTMIDI_OPMODEL_H

View file

@ -1,225 +1,92 @@
#include "importmidi_panel.h"
#include "ui_importmidi_panel.h"
#include "musescore.h"
#include "libmscore/score.h"
#include "preferences.h"
#include "importmidi_operations.h"
#include "importmidi_trmodel.h"
#include "importmidi_opmodel.h"
#include "importmidi_opdelegate.h"
#include "importmidi_data.h"
#include "importmidi_model.h"
#include "importmidi_lyrics.h"
#include "importmidi_inner.h"
#include "importmidi_operations.h"
#include "importmidi_delegate.h"
#include "preferences.h"
#include "musescore.h"
namespace Ms {
extern Score::FileError importMidi(Score*, const QString&);
extern QList<TrackMeta> extractMidiTracksMeta(const QString&);
extern MuseScore* mscore;
extern Preferences preferences;
ImportMidiPanel::ImportMidiPanel(QWidget *parent)
: QWidget(parent)
, ui(new Ui::ImportMidiPanel)
, updateUiTimer(new QTimer)
, importInProgress(false)
, prefferedVisible_(false)
, reopenInProgress(false)
, _ui(new Ui::ImportMidiPanel)
, _updateUiTimer(new QTimer)
, _prefferedVisible(false)
, _importInProgress(false)
, _reopenInProgress(false)
{
ui->setupUi(this);
tracksModel = new TracksModel();
operationsModel = new OperationsModel();
operationsDelegate = new OperationsDelegate(parentWidget(), false);
tracksDelegate = new OperationsDelegate(parentWidget(), true); // same class
ui->treeViewOperations->setModel(operationsModel);
ui->treeViewOperations->setItemDelegate(operationsDelegate);
ui->tableViewTracks->setModel(tracksModel);
ui->tableViewTracks->setItemDelegate(tracksDelegate);
tweakUi();
_ui->setupUi(this);
_model = new TracksModel();
_delegate = new OperationsDelegate(parentWidget(), false);
_ui->tracksView->setModel(_model);
_ui->tracksView->setItemDelegate(_delegate);
setupUi();
}
ImportMidiPanel::~ImportMidiPanel()
{
delete ui;
delete _ui;
}
void ImportMidiPanel::onCurrentTrackChanged(const QModelIndex &currentIndex)
void ImportMidiPanel::setMidiFile(const QString &fileName)
{
if (currentIndex.isValid()) {
const int row = currentIndex.row();
QString trackLabel = tracksModel->data(
tracksModel->index(row, TrackCol::TRACK_NUMBER)).toString();
operationsModel->setTrackData(trackLabel, tracksModel->trackOperations(row));
ui->treeViewOperations->expandAll();
preferences.midiImportOperations.midiData().setSelectedRow(midiFile, row);
}
}
void ImportMidiPanel::onOperationChanged(const QModelIndex &index)
{
const MidiOperation::Type operType = (MidiOperation::Type)index.data(OperationTypeRole).toInt();
const QModelIndex &currentIndex = ui->tableViewTracks->currentIndex();
tracksModel->setOperation(currentIndex.row(), operType, index.data(DataRole));
ui->treeViewOperations->expandAll();
// select first column to clear focus of current item
QModelIndex firstColIndex = operationsModel->index(index.row(), index.column() - 1,
index.parent());
ui->treeViewOperations->setCurrentIndex(firstColIndex);
}
// Сlass to add an extra width to specific columns
class CustomHorizHeaderView : public QHeaderView
{
public:
CustomHorizHeaderView() : QHeaderView(Qt::Horizontal) {}
protected:
QSize sectionSizeFromContents(int logicalIndex) const
{
auto sz = QHeaderView::sectionSizeFromContents(logicalIndex);
const int EXTRA_SPACE = 35;
if (logicalIndex == TrackCol::STAFF_NAME || logicalIndex == TrackCol::INSTRUMENT)
return QSize(sz.width() + EXTRA_SPACE, sz.height());
else
return sz;
}
};
void ImportMidiPanel::tweakUi()
{
connect(updateUiTimer, SIGNAL(timeout()), this, SLOT(updateUi()));
connect(ui->pushButtonImport, SIGNAL(clicked()), SLOT(doMidiImport()));
connect(ui->pushButtonUp, SIGNAL(clicked()), SLOT(moveTrackUp()));
connect(ui->pushButtonDown, SIGNAL(clicked()), SLOT(moveTrackDown()));
connect(ui->toolButtonHideMidiPanel, SIGNAL(clicked()), SLOT(hidePanel()));
const QItemSelectionModel *sm = ui->tableViewTracks->selectionModel();
connect(sm, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
SLOT(onCurrentTrackChanged(QModelIndex)));
connect(ui->treeViewOperations->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
SLOT(onOperationChanged(QModelIndex)));
updateUiTimer->start(100);
updateUi();
ui->tableViewTracks->verticalHeader()->setDefaultSectionSize(22);
ui->tableViewTracks->setHorizontalHeader(new CustomHorizHeaderView());
ui->tableViewTracks->horizontalHeader()->setResizeMode(TrackCol::TRACK_NUMBER,
QHeaderView::ResizeToContents);
ui->tableViewTracks->horizontalHeader()->setResizeMode(TrackCol::DO_IMPORT,
QHeaderView::ResizeToContents);
ui->tableViewTracks->horizontalHeader()->setResizeMode(TrackCol::LYRICS,
QHeaderView::Stretch);
ui->tableViewTracks->horizontalHeader()->setResizeMode(TrackCol::STAFF_NAME,
QHeaderView::Stretch);
ui->tableViewTracks->horizontalHeader()->setResizeMode(TrackCol::INSTRUMENT,
QHeaderView::Stretch);
ui->treeViewOperations->header()->resizeSection(0, 300);
ui->treeViewOperations->setAllColumnsShowFocus(true);
ui->comboBoxCharset->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
fillCharsetList();
}
bool ImportMidiPanel::canImportMidi() const
{
return isMidiFileExists() && tracksModel->numberOfTracksForImport();
}
void ImportMidiPanel::hidePanel()
{
if (isVisible()) {
setVisible(false);
emit closeClicked();
prefferedVisible_ = false;
}
}
bool ImportMidiPanel::canMoveTrackUp(int visualIndex)
{
return tracksModel->trackCount() > 1 && visualIndex > 1;
}
bool ImportMidiPanel::canMoveTrackDown(int visualIndex)
{
return tracksModel->trackCount() > 1
&& visualIndex < tracksModel->trackCount() && visualIndex > 0;
}
int ImportMidiPanel::currentVisualIndex()
{
const auto selectedRows = ui->tableViewTracks->selectionModel()->selectedRows();
int curRow = -1;
if (!selectedRows.isEmpty())
curRow = ui->tableViewTracks->selectionModel()->selectedRows()[0].row();
const int visIndex = ui->tableViewTracks->verticalHeader()->visualIndex(curRow);
return visIndex;
}
void ImportMidiPanel::moveTrackUp()
{
int visIndex = currentVisualIndex();
if (canMoveTrackUp(visIndex))
ui->tableViewTracks->verticalHeader()->moveSection(visIndex, visIndex - 1);
}
void ImportMidiPanel::moveTrackDown()
{
const int visIndex = currentVisualIndex();
if (canMoveTrackDown(visIndex))
ui->tableViewTracks->verticalHeader()->moveSection(visIndex, visIndex + 1);
}
void ImportMidiPanel::updateUi()
{
ui->pushButtonImport->setEnabled(canImportMidi());
const int visualIndex = currentVisualIndex();
ui->pushButtonUp->setEnabled(canMoveTrackUp(visualIndex));
ui->pushButtonDown->setEnabled(canMoveTrackDown(visualIndex));
}
QList<int> ImportMidiPanel::findReorderedIndexes()
{
QList<int> reorderedIndexes;
for (int i = 0; i != tracksModel->trackCount(); ++i) {
const int trackRow = tracksModel->rowFromTrackIndex(i);
const int reorderedRow = ui->tableViewTracks->verticalHeader()->logicalIndex(trackRow);
const int reorderedIndex = tracksModel->trackIndexFromRow(reorderedRow);
reorderedIndexes.push_back(reorderedIndex);
}
return reorderedIndexes;
}
void ImportMidiPanel::doMidiImport()
{
if (!canImportMidi())
if (_reopenInProgress)
_reopenInProgress = false;
if (_midiFile == fileName || _importInProgress)
return;
importInProgress = true;
const QList<int> reorderedIndexes = findReorderedIndexes();
QList<TrackData> trackData;
for (int i = 0; i != tracksModel->trackCount(); ++i) {
tracksModel->setTrackReorderedIndex(i, reorderedIndexes.indexOf(i));
trackData.push_back(tracksModel->trackData(i));
_midiFile = fileName;
updateUi();
if (!QFile(_midiFile).exists())
return;
MidiOperations::Data &opers = preferences.midiImportOperations;
opers.setCurrentMidiFile(_midiFile);
_model->reset(opers.data()->trackOpers,
MidiLyrics::makeLyricsListForUI(),
opers.data()->trackCount);
if (opers.data()->isNewlyOpened) { // open new MIDI file
resetTableViewState();
saveTableViewState();
}
else { // switch to already opened MIDI file
restoreTableViewState();
}
setMidiPrefOperations(trackData);
// update charset
preferences.midiImportOperations.midiData().setCharset(
midiFile, ui->comboBoxCharset->currentText());
tracksModel->forceColumnDataChanged(TrackCol::STAFF_NAME);
tracksModel->forceColumnDataChanged(TrackCol::LYRICS);
_ui->tracksView->setFrozenRowCount(_model->frozenRowCount());
_ui->tracksView->setFrozenColCount(_model->frozenColCount());
_ui->comboBoxCharset->setCurrentText(preferences.midiImportOperations.data()->charset);
_ui->tracksView->selectRow(opers.data()->selectedRow);
_ui->tracksView->selectColumn(0);
}
mscore->openScore(midiFile);
clearMidiPrefOperations();
preferences.midiImportOperations.midiData().setTracksData(midiFile, trackData);
saveTableViewState(midiFile);
importInProgress = false;
void ImportMidiPanel::saveTableViewState()
{
const QByteArray hData = _ui->tracksView->horizontalHeader()->saveState();
const QByteArray vData = _ui->tracksView->verticalHeader()->saveState();
preferences.midiImportOperations.data()->HHeaderData = hData;
preferences.midiImportOperations.data()->VHeaderData = vData;
}
void ImportMidiPanel::restoreTableViewState()
{
const QByteArray hData = preferences.midiImportOperations.data()->HHeaderData;
const QByteArray vData = preferences.midiImportOperations.data()->VHeaderData;
_ui->tracksView->horizontalHeader()->restoreState(hData);
_ui->tracksView->verticalHeader()->restoreState(vData);
}
void ImportMidiPanel::resetTableViewState()
{
_model->clear();
_ui->tracksView->verticalHeader()->reset();
}
bool ImportMidiPanel::isMidiFile(const QString &fileName)
@ -228,199 +95,148 @@ bool ImportMidiPanel::isMidiFile(const QString &fileName)
return (extension == "mid" || extension == "midi" || extension == "kar");
}
void ImportMidiPanel::saveTableViewState(const QString &fileName)
void ImportMidiPanel::setupUi()
{
const QByteArray hData = ui->tableViewTracks->horizontalHeader()->saveState();
const QByteArray vData = ui->tableViewTracks->verticalHeader()->saveState();
preferences.midiImportOperations.midiData().saveHHeaderState(fileName, hData);
preferences.midiImportOperations.midiData().saveVHeaderState(fileName, vData);
connect(_updateUiTimer, SIGNAL(timeout()), this, SLOT(updateUi()));
connect(_ui->pushButtonApply, SIGNAL(clicked()), SLOT(applyMidiImport()));
connect(_ui->pushButtonUp, SIGNAL(clicked()), SLOT(moveTrackUp()));
connect(_ui->pushButtonDown, SIGNAL(clicked()), SLOT(moveTrackDown()));
connect(_ui->toolButtonHideMidiPanel, SIGNAL(clicked()), SLOT(hidePanel()));
const QItemSelectionModel *sm = _ui->tracksView->selectionModel();
connect(sm, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
SLOT(onCurrentTrackChanged(QModelIndex)));
_updateUiTimer->start(100);
updateUi();
// tracks view
_ui->tracksView->verticalHeader()->setDefaultSectionSize(24);
// _ui->tracksView->horizontalHeader()->setResizeMode(TrackCol::DO_IMPORT,
// QHeaderView::ResizeToContents);
// _ui->tracksView->horizontalHeader()->setResizeMode(TrackCol::STAFF_NAME,
// QHeaderView::Stretch);
// _ui->tracksView->horizontalHeader()->setResizeMode(TrackCol::INSTRUMENT,
// QHeaderView::Stretch);
// charset
_ui->comboBoxCharset->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
fillCharsetList();
}
void ImportMidiPanel::restoreTableViewState(const QString &fileName)
void ImportMidiPanel::onCurrentTrackChanged(const QModelIndex &currentIndex)
{
const QByteArray hData = preferences.midiImportOperations.midiData().HHeaderData(fileName);
const QByteArray vData = preferences.midiImportOperations.midiData().VHeaderData(fileName);
ui->tableViewTracks->horizontalHeader()->restoreState(hData);
ui->tableViewTracks->verticalHeader()->restoreState(vData);
}
void ImportMidiPanel::setMidiPrefOperations(const QList<TrackData> &trackData)
{
clearMidiPrefOperations();
for (const auto &data: trackData)
preferences.midiImportOperations.appendTrackOperations(data.opers);
preferences.midiImportOperations.setCurrentMidiFile(midiFile);
}
void ImportMidiPanel::resetTableViewState()
{
tracksModel->clear();
ui->tableViewTracks->verticalHeader()->reset();
}
void ImportMidiPanel::clearMidiPrefOperations()
{
preferences.midiImportOperations.clear();
preferences.midiImportOperations.setCurrentMidiFile(midiFile);
}
bool ImportMidiPanel::isMidiFileExists() const
{
return preferences.midiImportOperations.midiData().midiFile(midiFile);
}
void ImportMidiPanel::setMidiPrefOperations(const QString &fileName)
{
if (importInProgress)
return;
reopenInProgress = true;
QList<TrackData> trackData
= preferences.midiImportOperations.midiData().tracksData(fileName);
setMidiPrefOperations(trackData);
preferences.midiImportOperations.setCurrentMidiFile(fileName);
}
void ImportMidiPanel::showOrHideStaffNameCol(const QList<TrackMeta> &tracksMeta)
{
bool emptyName = true;
for (const auto &meta: tracksMeta) {
if (!meta.staffName.empty()) {
emptyName = false;
break;
}
}
if (emptyName)
ui->tableViewTracks->horizontalHeader()->hideSection(TrackCol::STAFF_NAME);
else
ui->tableViewTracks->horizontalHeader()->showSection(TrackCol::STAFF_NAME);
}
void ImportMidiPanel::showOrHideLyricsCol(const QList<TrackData> &tracksData)
{
bool hasLyricsTrack = false;
for (const auto &data: tracksData) {
if (data.opers.lyricTrackIndex >= 0) {
hasLyricsTrack = true;
break;
}
}
if (hasLyricsTrack)
ui->tableViewTracks->horizontalHeader()->showSection(TrackCol::LYRICS);
else
ui->tableViewTracks->horizontalHeader()->hideSection(TrackCol::LYRICS);
if (currentIndex.isValid())
preferences.midiImportOperations.data()->selectedRow = currentIndex.row();
}
void ImportMidiPanel::fillCharsetList()
{
QFontMetrics fm(ui->comboBoxCharset->font());
QFontMetrics fm(_ui->comboBoxCharset->font());
ui->comboBoxCharset->clear();
_ui->comboBoxCharset->clear();
QList<QByteArray> charsets = QTextCodec::availableCodecs();
qSort(charsets.begin(), charsets.end());
int idx = 0;
int maxWidth = 0;
for (const auto &charset: charsets) {
ui->comboBoxCharset->addItem(charset);
_ui->comboBoxCharset->addItem(charset);
if (charset == MidiCharset::defaultCharset())
ui->comboBoxCharset->setCurrentIndex(idx);
_ui->comboBoxCharset->setCurrentIndex(idx);
int newWidth = fm.width(charset);
if (newWidth > maxWidth)
maxWidth = newWidth;
++idx;
}
ui->comboBoxCharset->view()->setMinimumWidth(maxWidth);
_ui->comboBoxCharset->view()->setMinimumWidth(maxWidth);
}
void ImportMidiPanel::setMidiFile(const QString &fileName)
void ImportMidiPanel::updateUi()
{
if (reopenInProgress)
reopenInProgress = false;
if (midiFile == fileName || importInProgress)
return;
midiFile = fileName;
updateUi();
_ui->pushButtonApply->setEnabled(canImportMidi());
if (isMidiFileExists()) {
auto &midiData = preferences.midiImportOperations.midiData();
QList<TrackData> trackData = midiData.tracksData(fileName);
if (trackData.isEmpty()) { // open new MIDI file
resetTableViewState();
clearMidiPrefOperations();
const QList<TrackMeta> tracksMeta = extractMidiTracksMeta(fileName);
tracksModel->reset(tracksMeta);
tracksModel->setLyricsList(MidiLyrics::makeLyricsListForUI());
// assign initial values of computed options to GUI model
const int row = 0; // for all tracks
const bool isHumanPerformance = midiData.isHumanPerformance(fileName);
tracksModel->setOperation(row, MidiOperation::Type::QUANT_HUMAN,
QVariant(isHumanPerformance));
const auto quantValue = midiData.quantValue(fileName);
tracksModel->setOperation(row, MidiOperation::Type::QUANT_VALUE,
QVariant((int)quantValue));
if (isHumanPerformance) {
const auto timeSig = midiData.timeSignature(fileName);
const int visualIndex = currentVisualIndex();
_ui->pushButtonUp->setEnabled(canMoveTrackUp(visualIndex));
_ui->pushButtonDown->setEnabled(canMoveTrackDown(visualIndex));
}
Q_ASSERT_X(timeSig != ReducedFraction(0, 1),
"ImportMidiPanel::setMidiFile", "Zero time signature");
const auto numerator = Meter::fractionNumeratorToUserValue(timeSig.numerator());
const auto denominator = Meter::fractionDenominatorToUserValue(timeSig.denominator());
tracksModel->setOperation(row, MidiOperation::Type::TIME_SIG_NUMERATOR,
QVariant((int)numerator));
tracksModel->setOperation(row, MidiOperation::Type::TIME_SIG_DENOMINATOR,
QVariant((int)denominator));
}
operationsModel->setTimeSigVisibility(isHumanPerformance);
for (int i = 0; i != tracksModel->trackCount(); ++i) {
const bool needSplit = midiData.needToSplit(fileName, i);
tracksModel->setOperation(
tracksModel->rowFromTrackIndex(i),
MidiOperation::Type::DO_LHRH_SEPARATION,
QVariant(needSplit));
}
showOrHideStaffNameCol(tracksMeta);
for (int i = 0; i != tracksModel->trackCount(); ++i)
trackData.push_back(tracksModel->trackData(i));
midiData.setTracksData(fileName, trackData);
showOrHideLyricsCol(trackData);
saveTableViewState(fileName);
}
else { // load previously saved data (tracks, operations) for this MIDI file
preferences.midiImportOperations.setCurrentMidiFile(midiFile);
tracksModel->reset(trackData);
tracksModel->setLyricsList(MidiLyrics::makeLyricsListForUI());
// time sig option is shown only for files
// that were initially detected as human-performed
const bool isHumanPerformance = midiData.isHumanPerformance(fileName);
operationsModel->setTimeSigVisibility(isHumanPerformance);
restoreTableViewState(fileName);
}
ui->comboBoxCharset->setCurrentText(preferences.midiImportOperations.charset());
ui->tableViewTracks->selectRow(
preferences.midiImportOperations.midiData().selectedRow(midiFile));
void ImportMidiPanel::hidePanel()
{
if (isVisible()) {
setVisible(false);
emit closeClicked();
_prefferedVisible = false;
}
}
void ImportMidiPanel::applyMidiImport()
{
if (!canImportMidi())
return;
_importInProgress = true;
MidiOperations::FileData *midiData = preferences.midiImportOperations.data();
// update charset
if (midiData->charset != _ui->comboBoxCharset->currentText()) {
midiData->charset = _ui->comboBoxCharset->currentText();
// need to update model because of charset change
_model->updateCharset();
}
mscore->openScore(_midiFile);
midiData->trackOpers = _model->trackOpers();
saveTableViewState();
_importInProgress = false;
}
bool ImportMidiPanel::canImportMidi() const
{
return QFile(_midiFile).exists() && _model->trackCountForImport() > 0;
}
bool ImportMidiPanel::canMoveTrackUp(int visualIndex) const
{
return _model->trackCount() > 1 && visualIndex > 1;
}
bool ImportMidiPanel::canMoveTrackDown(int visualIndex) const
{
return _model->trackCount() > 1
&& visualIndex < _model->trackCount() && visualIndex > 0;
}
int ImportMidiPanel::currentVisualIndex() const
{
const auto selectedRows = _ui->tracksView->selectionModel()->selectedRows();
int curRow = -1;
if (!selectedRows.isEmpty())
curRow = _ui->tracksView->selectionModel()->selectedRows()[0].row();
const int visIndex = _ui->tracksView->verticalHeader()->visualIndex(curRow);
return visIndex;
}
void ImportMidiPanel::excludeMidiFile(const QString &fileName)
{
// because button "Apply" of MIDI import operations
// causes reopen of the current score
// we need to prevent MIDI import panel from closing at that moment
if (importInProgress || reopenInProgress)
if (_importInProgress || _reopenInProgress)
return;
preferences.midiImportOperations.midiData().excludeFile(fileName);
if (fileName == midiFile) {
preferences.midiImportOperations.setCurrentMidiFile("");
midiFile = "";
auto &opers = preferences.midiImportOperations;
opers.excludeFile(fileName);
if (fileName == _midiFile) {
opers.setCurrentMidiFile("");
_midiFile = "";
}
}
void ImportMidiPanel::setPrefferedVisible(bool visible)
{
prefferedVisible_ = visible;
_prefferedVisible = visible;
}
void ImportMidiPanel::setReopenInProgress()
{
_reopenInProgress = true;
}
} // namespace Ms

View file

@ -6,16 +6,11 @@ namespace Ui {
class ImportMidiPanel;
}
class QModelIndex;
namespace Ms {
class TracksModel;
class OperationsModel;
class OperationsDelegate;
struct TrackData;
struct TrackMeta;
class ImportMidiPanel : public QWidget
{
@ -24,52 +19,44 @@ class ImportMidiPanel : public QWidget
public:
explicit ImportMidiPanel(QWidget *parent = 0);
~ImportMidiPanel();
static bool isMidiFile(const QString &fileName);
void setMidiFile(const QString &fileName);
void excludeMidiFile(const QString &fileName);
bool prefferedVisible() const { return prefferedVisible_; }
bool isPrefferedVisible() const { return _prefferedVisible; }
void setPrefferedVisible(bool visible);
void setMidiPrefOperations(const QString &fileName);
void setReopenInProgress();
static bool isMidiFile(const QString &fileName);
signals:
void closeClicked();
private slots:
void updateUi();
void onCurrentTrackChanged(const QModelIndex &currentIndex);
void onOperationChanged(const QModelIndex &index);
void doMidiImport();
void hidePanel();
void moveTrackUp();
void moveTrackDown();
bool canMoveTrackUp(int visualIndex);
bool canMoveTrackDown(int visualIndex);
void applyMidiImport();
void onCurrentTrackChanged(const QModelIndex &);
private:
void tweakUi();
void setupUi();
bool canImportMidi() const;
QList<int> findReorderedIndexes();
void saveTableViewState(const QString &fileName);
void restoreTableViewState(const QString &fileName);
bool canMoveTrackUp(int visualIndex) const;
bool canMoveTrackDown(int visualIndex) const;
int currentVisualIndex() const;
void saveTableViewState();
void restoreTableViewState();
void resetTableViewState();
int currentVisualIndex();
void setMidiPrefOperations(const QList<TrackData> &trackData);
void clearMidiPrefOperations();
bool isMidiFileExists() const;
void showOrHideStaffNameCol(const QList<TrackMeta> &tracksMeta);
void showOrHideLyricsCol(const QList<TrackData> &tracksData);
void fillCharsetList();
Ui::ImportMidiPanel *ui;
QTimer *updateUiTimer;
QString midiFile;
TracksModel *tracksModel;
OperationsModel *operationsModel;
OperationsDelegate *operationsDelegate;
OperationsDelegate *tracksDelegate;
bool importInProgress;
bool prefferedVisible_;
bool reopenInProgress;
Ui::ImportMidiPanel *_ui;
QTimer *_updateUiTimer;
TracksModel *_model;
OperationsDelegate *_delegate;
bool _prefferedVisible;
bool _importInProgress;
bool _reopenInProgress;
QString _midiFile;
};
} // namespace Ms

View file

@ -200,7 +200,7 @@
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButtonImport">
<widget class="QPushButton" name="pushButtonApply">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -249,59 +249,7 @@
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>2</number>
</property>
<widget class="TracksView" name="tableViewTracks">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::CurrentChanged|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
<widget class="QTreeView" name="treeViewOperations">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::CurrentChanged|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="tabKeyNavigation">
<bool>true</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</widget>
</widget>
<widget class="TracksView" name="tracksView"/>
</item>
</layout>
</widget>
@ -309,7 +257,7 @@
<customwidget>
<class>TracksView</class>
<extends>QTableView</extends>
<header>mscore/importmidi_trview.h</header>
<header>mscore/importmidi_view.h</header>
</customwidget>
</customwidgets>
<resources>

View file

@ -9,6 +9,7 @@
#include "importmidi_inner.h"
#include "importmidi_beat.h"
#include "importmidi_voice.h"
#include "importmidi_operations.h"
#include <set>
#include <deque>
@ -20,31 +21,31 @@ extern Preferences preferences;
namespace Quantize {
ReducedFraction userQuantNoteToFraction(MidiOperation::QuantValue quantNote)
ReducedFraction userQuantNoteToFraction(MidiOperations::QuantValue quantNote)
{
const auto division = ReducedFraction::fromTicks(MScore::division);
auto userQuantValue = ReducedFraction::fromTicks(preferences.shortestNote);
// specified quantization value
switch (quantNote) {
case MidiOperation::QuantValue::N_4:
case MidiOperations::QuantValue::Q_4:
userQuantValue = division;
break;
case MidiOperation::QuantValue::N_8:
case MidiOperations::QuantValue::Q_8:
userQuantValue = division / 2;
break;
case MidiOperation::QuantValue::N_16:
case MidiOperations::QuantValue::Q_16:
userQuantValue = division / 4;
break;
case MidiOperation::QuantValue::N_32:
case MidiOperations::QuantValue::Q_32:
userQuantValue = division / 8;
break;
case MidiOperation::QuantValue::N_64:
case MidiOperations::QuantValue::Q_64:
userQuantValue = division / 16;
break;
case MidiOperation::QuantValue::N_128:
case MidiOperations::QuantValue::Q_128:
userQuantValue = division / 32;
break;
case MidiOperation::QuantValue::FROM_PREFERENCES:
case MidiOperations::QuantValue::FROM_PREFERENCES:
default:
break;
}
@ -366,9 +367,11 @@ void setIfHumanPerformance(
if (allChords.empty())
return;
const bool isHuman = isHumanPerformance(allChords, sigmap);
preferences.midiImportOperations.setHumanPerformance(isHuman);
auto &opers = preferences.midiImportOperations.data()->trackOpers;
opers.isHumanPerformance = isHuman;
if (isHuman) {
preferences.midiImportOperations.setQuantValue(MidiOperation::QuantValue::N_8);
opers.quantValue.setDefaultValue(MidiOperations::QuantValue::Q_8);
const double ticksPerSec = MidiTempo::findBasicTempo(tracks) * MScore::division;
MidiBeat::findBeatLocations(allChords, sigmap, ticksPerSec); // and set time sig
}
@ -992,8 +995,8 @@ double findTempoPenalty(
void applyDynamicProgramming(std::vector<QuantData> &quantData)
{
const auto &opers = preferences.midiImportOperations.currentTrackOperations();
const bool isHuman = opers.quantize.humanPerformance;
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const bool isHuman = opers.isHumanPerformance;
const double MERGE_PENALTY_COEFF = 5.0;
for (int chordIndex = 0; chordIndex != (int)quantData.size(); ++chordIndex) {

View file

@ -13,7 +13,7 @@ class ReducedFraction;
namespace Quantize {
ReducedFraction userQuantNoteToFraction(MidiOperation::QuantValue quantNote);
ReducedFraction userQuantNoteToFraction(MidiOperations::QuantValue quantNote);
ReducedFraction findQuantForRange(
const std::multimap<ReducedFraction, MidiChord>::const_iterator &beg,

View file

@ -5,10 +5,12 @@
#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 {
@ -57,8 +59,10 @@ void lengthenNote(
if (endTime <= note.offTime)
return;
const auto &opers = preferences.midiImportOperations.currentTrackOperations();
const bool useDots = opers.useDots;
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);
@ -111,7 +115,7 @@ void lengthenNote(
if (noteDurationCount + restDurationCount
< minNoteDurationCount + minRestDurationCount) {
if (opers.quantize.humanPerformance || noteDurationCount <= 1.5) {
if (opers.isHumanPerformance || noteDurationCount <= 1.5) {
minNoteDurationCount = noteDurationCount;
minRestDurationCount = restDurationCount;
bestOffTime = offTime;
@ -142,7 +146,7 @@ void lengthenNote(
// discard change because it silently reduces duration accuracy
// without significant improvement of readability
if (!opers.quantize.humanPerformance
if (!opers.isHumanPerformance
&& (origNoteDurations.size() + origRestDurations.size())
- (minNoteDurationCount + minRestDurationCount) <= 1
&& !hasComplexBeamedDurations(origNoteDurations)
@ -220,11 +224,9 @@ void simplifyDurations(std::multimap<int, MTrack> &tracks, const TimeSigMap *sig
auto &chords = track.second.chords;
if (chords.empty())
continue;
// pass current track index through MidiImportOperations
// for further usage
opers.setCurrentTrack(mtrack.indexOfOperation);
if (opers.currentTrackOperations().simplifyDurations) {
if (opers.data()->trackOpers.simplifyDurations.value(mtrack.indexOfOperation)) {
MidiOperations::CurrentTrackSetter setCurrentTrack{opers, mtrack.indexOfOperation};
minimizeNumberOfRests(chords, sigmap, mtrack.tuplets);
// empty tuplets may appear after simplification
MidiTuplet::removeEmptyTuplets(mtrack);

View file

@ -16,7 +16,7 @@ namespace Swing {
class SwingDetector
{
public:
SwingDetector(MidiOperation::Swing st);
SwingDetector(MidiOperations::Swing st);
void add(ChordRest *cr);
bool wasSwingApplied() const { return swingApplied; }
@ -25,7 +25,7 @@ class SwingDetector
std::vector<ChordRest *> elements;
ReducedFraction sumLen;
const ReducedFraction FULL_LEN = {1, 4};
MidiOperation::Swing swingType;
MidiOperations::Swing swingType;
bool swingApplied = false;
void reset();
@ -38,7 +38,7 @@ class SwingDetector
};
SwingDetector::SwingDetector(MidiOperation::Swing st)
SwingDetector::SwingDetector(MidiOperations::Swing st)
: swingType(st)
{
}
@ -61,10 +61,10 @@ void SwingDetector::add(ChordRest *cr)
if (sumLen == FULL_LEN) {
// check for swing patterns
switch (swingType) {
case MidiOperation::Swing::SWING:
case MidiOperations::Swing::SWING:
checkNormalSwing();
break;
case MidiOperation::Swing::SHUFFLE:
case MidiOperations::Swing::SHUFFLE:
checkShuffle();
break;
default:
@ -192,23 +192,23 @@ bool SwingDetector::areAllNonTuplets() const
// ---------------------------------------------------------------
QString swingCaption(MidiOperation::Swing swingType)
QString swingCaption(MidiOperations::Swing swingType)
{
QString caption;
switch (swingType) {
case MidiOperation::Swing::SWING:
case MidiOperations::Swing::SWING:
caption = "Swing";
break;
case MidiOperation::Swing::SHUFFLE:
case MidiOperations::Swing::SHUFFLE:
caption = "Shuffle";
break;
case MidiOperation::Swing::NONE:
case MidiOperations::Swing::NONE:
break;
}
return caption;
}
void detectSwing(Staff *staff, MidiOperation::Swing swingType)
void detectSwing(Staff *staff, MidiOperations::Swing swingType)
{
Score *score = staff->score();
const int strack = staff->idx() * VOICES;

View file

@ -10,7 +10,7 @@ class Staff;
namespace Swing {
void detectSwing(Staff *staff, MidiOperation::Swing swingType);
void detectSwing(Staff *staff, MidiOperations::Swing swingType);
} // namespace Swing
} // namespace Ms

View file

@ -1,644 +0,0 @@
#include "importmidi_trmodel.h"
#include "importmidi_operations.h"
#include "importmidi_inner.h"
namespace Ms {
TracksModel::TracksModel()
: trackCount_(0)
, colCount_(TrackCol::TCOL_COUNT)
{
}
void TracksModel::reset(const QList<TrackMeta> &tracksMeta)
{
beginResetModel();
trackCount_ = tracksMeta.size();
tracksData_.clear();
int i = 0;
for (const auto &meta: tracksMeta) {
TrackOperations ops; // initialized by default values - see ctor
ops.reorderedIndex = i++;
ops.lyricTrackIndex = meta.initLyricTrackIndex;
tracksData_.push_back({meta, ops});
}
endResetModel();
}
void TracksModel::reset(const QList<TrackData> &tracksData)
{
beginResetModel();
trackCount_ = tracksData.size();
tracksData_ = tracksData;
endResetModel();
}
void TracksModel::clear()
{
beginResetModel();
trackCount_ = 0;
tracksData_.clear();
endResetModel();
}
void TracksModel::setOperation(int row, MidiOperation::Type operType, const QVariant &operValue)
{
const int trackIndex = trackIndexFromRow(row);
if (trackIndex == -1)
setOperationForAllTracks(operType, operValue);
else if (isTrackIndexValid(trackIndex))
setTrackOperation(trackIndex, operType, operValue);
}
void TracksModel::setTrackReorderedIndex(int trackIndex, int reorderIndex)
{
if (!isTrackIndexValid(trackIndex))
return;
tracksData_[trackIndex].opers.reorderedIndex = reorderIndex;
}
void TracksModel::setLyricsList(const QList<std::string> &list)
{
lyricsList_ = list;
}
void TracksModel::setOperationForAllTracks(MidiOperation::Type operType,
const QVariant &operValue)
{
for (int i = 0; i != trackCount_; ++i)
setTrackOperation(i, operType, operValue);
}
void TracksModel::setTrackOperation(int trackIndex, MidiOperation::Type operType,
const QVariant &operValue)
{
if (!operValue.isValid() || !isTrackIndexValid(trackIndex))
return;
TrackData &trackData = tracksData_[trackIndex];
switch (operType) {
case MidiOperation::Type::QUANT_VALUE:
trackData.opers.quantize.value = (MidiOperation::QuantValue)operValue.toInt();
break;
case MidiOperation::Type::QUANT_HUMAN:
trackData.opers.quantize.humanPerformance = operValue.toBool();
break;
case MidiOperation::Type::TIME_SIG_NUMERATOR:
trackData.opers.timeSig.numerator = (MidiOperation::TimeSigNumerator)operValue.toInt();
break;
case MidiOperation::Type::TIME_SIG_DENOMINATOR:
trackData.opers.timeSig.denominator = (MidiOperation::TimeSigDenominator)operValue.toInt();
break;
case MidiOperation::Type::DO_LHRH_SEPARATION:
trackData.opers.LHRH.doIt = operValue.toBool();
break;
case MidiOperation::Type::SPLIT_DRUMS:
trackData.opers.splitDrums.doSplit = operValue.toBool();
break;
case MidiOperation::Type::SHOW_STAFF_BRACKET:
trackData.opers.splitDrums.showStaffBracket = operValue.toBool();
break;
case MidiOperation::Type::REMOVE_DRUM_RESTS:
trackData.opers.removeDrumRests = operValue.toBool();
break;
case MidiOperation::Type::LHRH_METHOD:
trackData.opers.LHRH.method = (MidiOperation::LHRHMethod)operValue.toInt();
break;
case MidiOperation::Type::LHRH_SPLIT_OCTAVE:
trackData.opers.LHRH.splitPitchOctave
= (MidiOperation::Octave)operValue.toInt();
break;
case MidiOperation::Type::LHRH_SPLIT_NOTE:
trackData.opers.LHRH.splitPitchNote = (MidiOperation::Note)operValue.toInt();
break;
case MidiOperation::Type::USE_DOTS:
trackData.opers.useDots = operValue.toBool();
break;
case MidiOperation::Type::SIMPLIFY_DURATIONS:
trackData.opers.simplifyDurations = operValue.toBool();
break;
case MidiOperation::Type::SHOW_STACCATO:
trackData.opers.showStaccato = operValue.toBool();
break;
case MidiOperation::Type::SEPARATE_VOICES:
trackData.opers.separateVoices = operValue.toBool();
break;
case MidiOperation::Type::SWING:
trackData.opers.swing = (MidiOperation::Swing)operValue.toInt();
break;
case MidiOperation::Type::ALLOWED_VOICES:
trackData.opers.allowedVoices = (MidiOperation::AllowedVoices)operValue.toInt();
break;
case MidiOperation::Type::TUPLET_SEARCH:
trackData.opers.tuplets.doSearch = operValue.toBool();
break;
case MidiOperation::Type::TUPLET_2:
trackData.opers.tuplets.duplets = operValue.toBool();
break;
case MidiOperation::Type::TUPLET_3:
trackData.opers.tuplets.triplets = operValue.toBool();
break;
case MidiOperation::Type::TUPLET_4:
trackData.opers.tuplets.quadruplets = operValue.toBool();
break;
case MidiOperation::Type::TUPLET_5:
trackData.opers.tuplets.quintuplets = operValue.toBool();
break;
case MidiOperation::Type::TUPLET_7:
trackData.opers.tuplets.septuplets = operValue.toBool();
break;
case MidiOperation::Type::TUPLET_9:
trackData.opers.tuplets.nonuplets = operValue.toBool();
break;
case MidiOperation::Type::CHANGE_CLEF:
trackData.opers.changeClef = operValue.toBool();
break;
case MidiOperation::Type::PICKUP_MEASURE:
trackData.opers.pickupMeasure = operValue.toBool();
break;
case MidiOperation::Type::DO_IMPORT:
case MidiOperation::Type::LYRIC_TRACK_INDEX:
break;
}
}
DefinedTrackOperations TracksModel::trackOperations(int row) const
{
DefinedTrackOperations opers;
const int trackIndex = trackIndexFromRow(row);
opers.allTracksSelected = (trackIndex == -1 || trackCount_ == 1);
if (trackIndex == -1) {
// all tracks row case
// find tracks that operation values are different
// and mark them as undefined
opers.opers = tracksData_.front().opers;
opers.isDrumTrack = false;
// MidiOperation::Type::QUANT_VALUE
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.quantize.value != opers.opers.quantize.value) {
opers.undefinedOpers.insert((int)MidiOperation::Type::QUANT_VALUE);
break;
}
}
// MidiOperation::Type::QUANT_HUMAN
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.quantize.humanPerformance
!= opers.opers.quantize.humanPerformance) {
opers.undefinedOpers.insert((int)MidiOperation::Type::QUANT_HUMAN);
break;
}
}
// MidiOperation::Type::TIME_SIG_NUMERATOR
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.timeSig.numerator != opers.opers.timeSig.numerator) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TIME_SIG_NUMERATOR);
break;
}
}
// MidiOperation::Type::TIME_SIG_DENOMINATOR
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.timeSig.denominator != opers.opers.timeSig.denominator) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TIME_SIG_DENOMINATOR);
break;
}
}
// MidiOperation::Type::DO_LHRH_SEPARATION
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.LHRH.doIt != opers.opers.LHRH.doIt) {
opers.undefinedOpers.insert((int)MidiOperation::Type::DO_LHRH_SEPARATION);
break;
}
}
// MidiOperation::Type::SPLIT_DRUMS
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.splitDrums.doSplit != opers.opers.splitDrums.doSplit) {
opers.undefinedOpers.insert((int)MidiOperation::Type::SPLIT_DRUMS);
break;
}
}
// MidiOperation::Type::SHOW_STAFF_BRACKET
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.splitDrums.showStaffBracket != opers.opers.splitDrums.showStaffBracket) {
opers.undefinedOpers.insert((int)MidiOperation::Type::SHOW_STAFF_BRACKET);
break;
}
}
// MidiOperation::Type::REMOVE_DRUM_RESTS
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.removeDrumRests != opers.opers.removeDrumRests) {
opers.undefinedOpers.insert((int)MidiOperation::Type::REMOVE_DRUM_RESTS);
break;
}
}
// MidiOperation::Type::LHRH_METHOD
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.LHRH.method != opers.opers.LHRH.method) {
opers.undefinedOpers.insert((int)MidiOperation::Type::LHRH_METHOD);
break;
}
}
// MidiOperation::Type::LHRH_SPLIT_OCTAVE
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.LHRH.splitPitchOctave
!= opers.opers.LHRH.splitPitchOctave) {
opers.undefinedOpers.insert((int)MidiOperation::Type::LHRH_SPLIT_OCTAVE);
break;
}
}
// MidiOperation::Type::LHRH_SPLIT_NOTE
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.LHRH.splitPitchNote
!= opers.opers.LHRH.splitPitchNote) {
opers.undefinedOpers.insert((int)MidiOperation::Type::LHRH_SPLIT_NOTE);
break;
}
}
// MidiOperation::Type::USE_DOTS
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.useDots != opers.opers.useDots) {
opers.undefinedOpers.insert((int)MidiOperation::Type::USE_DOTS);
break;
}
}
// MidiOperation::Type::SIMPLIFY_DURATIONS
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.simplifyDurations != opers.opers.simplifyDurations) {
opers.undefinedOpers.insert((int)MidiOperation::Type::SIMPLIFY_DURATIONS);
break;
}
}
// MidiOperation::Type::SHOW_STACCATO
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.simplifyDurations != opers.opers.showStaccato) {
opers.undefinedOpers.insert((int)MidiOperation::Type::SHOW_STACCATO);
break;
}
}
// MidiOperation::Type::SEPARATE_VOICES
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.separateVoices != opers.opers.separateVoices) {
opers.undefinedOpers.insert((int)MidiOperation::Type::SEPARATE_VOICES);
break;
}
}
// MidiOperation::Type::SWING
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.swing != opers.opers.swing) {
opers.undefinedOpers.insert((int)MidiOperation::Type::SWING);
break;
}
}
// MidiOperation::Type::ALLOWED_VOICES
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.allowedVoices != opers.opers.allowedVoices) {
opers.undefinedOpers.insert((int)MidiOperation::Type::ALLOWED_VOICES);
break;
}
}
// MidiOperation::Type::TUPLET_SEARCH
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.tuplets.doSearch != opers.opers.tuplets.doSearch) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TUPLET_SEARCH);
break;
}
}
// MidiOperation::Type::TUPLET_2
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.tuplets.duplets != opers.opers.tuplets.duplets) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TUPLET_2);
break;
}
}
// MidiOperation::Type::TUPLET_3
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.tuplets.triplets != opers.opers.tuplets.triplets) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TUPLET_3);
break;
}
}
// MidiOperation::Type::TUPLET_4
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.tuplets.quadruplets != opers.opers.tuplets.quadruplets) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TUPLET_4);
break;
}
}
// MidiOperation::Type::TUPLET_5
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.tuplets.quintuplets != opers.opers.tuplets.quintuplets) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TUPLET_5);
break;
}
}
// MidiOperation::Type::TUPLET_7
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.tuplets.septuplets != opers.opers.tuplets.septuplets) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TUPLET_7);
break;
}
}
// MidiOperation::Type::TUPLET_9
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.tuplets.nonuplets != opers.opers.tuplets.nonuplets) {
opers.undefinedOpers.insert((int)MidiOperation::Type::TUPLET_9);
break;
}
}
// MidiOperation::Type::CHANGE_CLEF
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.changeClef != opers.opers.changeClef) {
opers.undefinedOpers.insert((int)MidiOperation::Type::CHANGE_CLEF);
break;
}
}
// MidiOperation::Type::PICKUP_MEASURE
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.pickupMeasure != opers.opers.pickupMeasure) {
opers.undefinedOpers.insert((int)MidiOperation::Type::PICKUP_MEASURE);
break;
}
}
}
else {
opers.opers = tracksData_[trackIndex].opers;
opers.isDrumTrack = tracksData_[trackIndex].meta.isDrumTrack;
}
return opers;
}
int TracksModel::rowFromTrackIndex(int trackIndex) const
{
// first row reserved for all tracks if track count > 1
return (trackCount_ > 1) ? trackIndex + 1 : trackIndex;
}
int TracksModel::trackIndexFromRow(int row) const
{
// first row reserved for all tracks if track count > 1
// return -1 if row is all tracks row
return (trackCount_ > 1) ? row - 1 : row;
}
int TracksModel::numberOfTracksForImport() const
{
int count = 0;
for (int i = 0; i != trackCount_; ++i) {
if (tracksData_[i].opers.doImport)
++count;
}
return count;
}
int TracksModel::rowCount(const QModelIndex &/*parent*/) const
{
return (trackCount_ > 1) ? trackCount_ + 1 : trackCount_;
}
int TracksModel::columnCount(const QModelIndex &/*parent*/) const
{
return colCount_;
}
Qt::CheckState TracksModel::areAllTracksForImport() const
{
if (trackCount_ == 0)
return Qt::Unchecked;
const bool doFirstTrackImport = tracksData_[0].opers.doImport;
for (int i = 1; i != trackCount_; ++i) {
if (tracksData_[i].opers.doImport != doFirstTrackImport)
return Qt::PartiallyChecked;
}
return (doFirstTrackImport) ? Qt::Checked : Qt::Unchecked;
}
QVariant TracksModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
const int trackIndex = trackIndexFromRow(index.row());
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
case TrackCol::TRACK_NUMBER:
if (trackIndex == -1)
return QCoreApplication::translate("MIDI import operations", "All");
return trackIndex + 1;
case TrackCol::LYRICS:
{
if (trackIndex == -1)
return "";
int lyricTrack = tracksData_[trackIndex].opers.lyricTrackIndex;
if (lyricTrack == -1)
return "";
if (lyricTrack >= 0 && lyricTrack < lyricsList_.size())
return MidiCharset::convertToCharset(lyricsList_[lyricTrack]);
}
break;
case TrackCol::STAFF_NAME:
if (trackIndex == -1)
return "";
return MidiCharset::convertToCharset(
tracksData_[trackIndex].meta.staffName);
case TrackCol::INSTRUMENT:
if (trackIndex == -1)
return "";
return tracksData_[trackIndex].meta.instrumentName;
default:
break;
}
break;
case Qt::EditRole:
if (index.column() == TrackCol::LYRICS && trackIndex != -1) {
if (!lyricsList_.isEmpty()) {
auto list = QStringList("");
for (const auto &lyric: lyricsList_)
list.append(MidiCharset::convertToCharset(lyric));
return list;
}
}
break;
case Qt::CheckStateRole:
switch (index.column()) {
case TrackCol::DO_IMPORT:
if (trackIndex == -1)
return areAllTracksForImport();
return (tracksData_[trackIndex].opers.doImport)
? Qt::Checked : Qt::Unchecked;
default:
break;
}
break;
case Qt::TextAlignmentRole:
if (index.column() == TrackCol::LYRICS)
return Qt::AlignLeft + Qt::AlignVCenter;
else
return Qt::AlignCenter;
break;
case Qt::ToolTipRole:
if (trackIndex != -1) {
switch (index.column()) {
case TrackCol::STAFF_NAME:
return MidiCharset::convertToCharset(
tracksData_[trackIndex].meta.staffName);
case TrackCol::INSTRUMENT:
return tracksData_[trackIndex].meta.instrumentName;
default:
break;
}
}
break;
default:
break;
}
return QVariant();
}
Qt::ItemFlags TracksModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
Qt::ItemFlags flags = Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
if (index.column() == TrackCol::DO_IMPORT)
flags |= Qt::ItemIsUserCheckable;
if (index.column() == TrackCol::LYRICS
&& !lyricsList_.isEmpty()
&& trackIndexFromRow(index.row()) != -1) {
flags |= Qt::ItemIsEditable;
}
return flags;
}
void TracksModel::forceColumnDataChanged(int col)
{
const auto begIndex = this->index(0, col);
const auto endIndex = this->index(rowCount(QModelIndex()), col);
emit dataChanged(begIndex, endIndex);
}
bool TracksModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
bool result = false;
const int trackIndex = trackIndexFromRow(index.row());
if (trackIndex == -1) { // all tracks row
if (index.column() == TrackCol::DO_IMPORT && role == Qt::CheckStateRole) {
for (auto &trackData: tracksData_)
trackData.opers.doImport = value.toBool();
result = true;
}
if (result) {
// update checkboxes of all tracks
// because we've changed option for all tracks simultaneously
forceColumnDataChanged(TrackCol::DO_IMPORT);
}
}
else {
TrackData *trackData = trackDataFromIndex(index);
if (!trackData)
return false;
if (index.column() == TrackCol::DO_IMPORT && role == Qt::CheckStateRole) {
trackData->opers.doImport = value.toBool();
result = true;
}
else if (index.column() == TrackCol::LYRICS && role == Qt::EditRole) {
trackData->opers.lyricTrackIndex = value.toInt() - 1;
result = true;
}
if (result) {
// update checkbox of current track row
emit dataChanged(index, index);
// update checkbox of <all tracks> row
const auto allIndex = this->index(0, TrackCol::DO_IMPORT);
emit dataChanged(allIndex, allIndex);
}
}
return result;
}
QVariant TracksModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case TrackCol::DO_IMPORT:
return QCoreApplication::translate("MIDI import track list", "Import");
case TrackCol::TRACK_NUMBER:
return QCoreApplication::translate("MIDI import track list", "Track");
case TrackCol::LYRICS:
return QCoreApplication::translate("MIDI import track list", "Lyrics");
case TrackCol::STAFF_NAME:
return QCoreApplication::translate("MIDI import track list", "Staff Name");
case TrackCol::INSTRUMENT:
return QCoreApplication::translate("MIDI import track list", "Sound");
default:
break;
}
}
return QVariant();
}
TrackData TracksModel::trackData(int trackIndex) const
{
if (isTrackIndexValid(trackIndex))
return tracksData_[trackIndex];
return TrackData();
}
TrackData* TracksModel::trackDataFromIndex(const QModelIndex &index)
{
if (index.isValid()) {
if (!isMappingRowToTrackValid(index.row()) || !isColumnValid(index.column()))
return nullptr;
return &tracksData_[trackIndexFromRow(index.row())];
}
return nullptr;
}
bool TracksModel::isMappingRowToTrackValid(int row) const
{
if (trackCount_ > 1) // first row is reserved for all tracks
return (row > 0 && row <= trackCount_);
return row >= 0 && row < trackCount_;
}
bool TracksModel::isColumnValid(int column) const
{
return (column >= 0 && column < colCount_);
}
bool TracksModel::isTrackIndexValid(int trackIndex) const
{
return trackIndex >= 0 && trackIndex < trackCount_;
}
} // namespace Ms

View file

@ -1,68 +0,0 @@
#ifndef IMPORTMIDI_TRMODEL_H
#define IMPORTMIDI_TRMODEL_H
#include "importmidi_operation.h"
namespace Ms {
struct TrackMeta;
struct TrackData;
struct TrackOperations;
struct DefinedTrackOperations;
enum TrackCol : char {
DO_IMPORT = 0,
TRACK_NUMBER,
STAFF_NAME,
INSTRUMENT,
LYRICS,
TCOL_COUNT
};
class TracksModel : public QAbstractTableModel
{
public:
TracksModel();
void reset(const QList<TrackMeta> &tracksMeta);
void reset(const QList<TrackData> &tracksData);
void clear();
void setOperation(int row, MidiOperation::Type operType, const QVariant &operValue);
void setTrackReorderedIndex(int trackIndex, int reorderIndex);
void setLyricsList(const QList<std::string> &list);
TrackData trackData(int trackIndex) const;
DefinedTrackOperations trackOperations(int row) const;
int trackCount() const { return trackCount_; }
int rowFromTrackIndex(int trackIndex) const;
int trackIndexFromRow(int row) const;
int numberOfTracksForImport() const;
int rowCount(const QModelIndex &/*parent*/) const;
int columnCount(const QModelIndex &/*parent*/) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
void forceColumnDataChanged(int col);
private:
QList<TrackData> tracksData_;
int trackCount_;
int colCount_;
QList<std::string> lyricsList_;
void setTrackOperation(int trackIndex, MidiOperation::Type operType, const QVariant &operValue);
void setOperationForAllTracks(MidiOperation::Type operType, const QVariant &operValue);
Qt::CheckState areAllTracksForImport() const;
TrackData* trackDataFromIndex(const QModelIndex &index);
bool isMappingRowToTrackValid(int row) const;
bool isColumnValid(int column) const;
bool isTrackIndexValid(int trackIndex) const;
};
} // namespace Ms
#endif // IMPORTMIDI_TRMODEL_H

View file

@ -1,28 +0,0 @@
#include "importmidi_trview.h"
TracksView::TracksView(QWidget *parent)
: QTableView(parent)
{
}
// show tooltip if the text is wider than the table cell
bool TracksView::viewportEvent(QEvent *event)
{
if (event->type() == QEvent::ToolTip) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
QModelIndex index = indexAt(helpEvent->pos());
if (index.isValid()) {
QSize sizeHint = itemDelegate(index)->sizeHint(viewOptions(), index);
QRect rItem(0, 0, sizeHint.width(), sizeHint.height());
QRect rVisual = visualRect(index);
if (rItem.width() <= rVisual.width()) {
QToolTip::hideText();
return false;
}
}
}
return QTableView::viewportEvent(event);
}

View file

@ -1,17 +0,0 @@
#ifndef IMPORTMIDI_TRVIEW_H
#define IMPORTMIDI_TRVIEW_H
#include <QTableView>
class TracksView : public QTableView
{
public:
explicit TracksView(QWidget *parent);
protected:
bool viewportEvent(QEvent *event);
};
#endif // IMPORTMIDI_TRVIEW_H

View file

@ -5,6 +5,8 @@
#include "importmidi_chord.h"
#include "importmidi_quant.h"
#include "importmidi_inner.h"
#include "importmidi_operations.h"
#include "libmscore/sig.h"
#include "preferences.h"
#include <set>
@ -1021,8 +1023,9 @@ void findTuplets(
{
if (chords.empty() || startBarTick >= endBarTick) // invalid cases
return;
const auto operations = preferences.midiImportOperations.currentTrackOperations();
if (!operations.tuplets.doSearch)
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
if (!opers.searchTuplets.value(currentTrack))
return;
const auto tol = basicQuant / 2;
@ -1041,7 +1044,7 @@ void findTuplets(
// later notes will be sorted and their indexes become invalid
// so assign staccato information to notes now
if (operations.simplifyDurations)
if (opers.simplifyDurations.value(currentTrack))
markStaccatoTupletNotes(tuplets);
// because of tol for non-tuplets we should use only chords with onTime >= bar start
@ -1066,7 +1069,7 @@ void findTuplets(
minimizeOffTimeError(tuplets, chords, nonTuplets, startBarTick, basicQuant, barIndex);
}
if (operations.simplifyDurations)
if (opers.simplifyDurations.value(currentTrack))
cleanStaccatoOfNonTuplets(nonTuplets);
Q_ASSERT_X(!doTupletsHaveCommonChords(tuplets),

View file

@ -4,6 +4,7 @@
#include "importmidi_chord.h"
#include "importmidi_quant.h"
#include "importmidi_inner.h"
#include "importmidi_operations.h"
#include "preferences.h"
#include <set>
@ -34,9 +35,8 @@ bool isTupletAllowed(const TupletInfo &tupletInfo)
}
}
// for all tuplets
const bool isHumanPerformance = preferences.midiImportOperations
.currentTrackOperations().quantize.humanPerformance;
const int minAllowedNoteCount = (isHumanPerformance)
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int minAllowedNoteCount = (opers.isHumanPerformance)
? tupletLimits(tupletInfo.tupletNumber).minNoteCountHuman
: tupletLimits(tupletInfo.tupletNumber).minNoteCount;
if ((int)tupletInfo.chords.size() < minAllowedNoteCount)
@ -65,23 +65,24 @@ bool isTupletAllowed(const TupletInfo &tupletInfo)
std::vector<int> findTupletNumbers(const ReducedFraction &divLen,
const ReducedFraction &barFraction)
{
const auto operations = preferences.midiImportOperations.currentTrackOperations();
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
std::vector<int> tupletNumbers;
if (Meter::isCompound(barFraction) && divLen == Meter::beatLength(barFraction)) {
if (operations.tuplets.duplets)
if (opers.search2plets.value(currentTrack))
tupletNumbers.push_back(2);
if (operations.tuplets.quadruplets)
if (opers.search4plets.value(currentTrack))
tupletNumbers.push_back(4);
}
else {
if (operations.tuplets.triplets)
if (opers.search3plets.value(currentTrack))
tupletNumbers.push_back(3);
if (operations.tuplets.quintuplets)
if (opers.search5plets.value(currentTrack))
tupletNumbers.push_back(5);
if (operations.tuplets.septuplets)
if (opers.search7plets.value(currentTrack))
tupletNumbers.push_back(7);
if (operations.tuplets.nonuplets)
if (opers.search9plets.value(currentTrack))
tupletNumbers.push_back(9);
}
@ -99,10 +100,12 @@ ReducedFraction findSumLengthOfRests(
const auto tupletEndTime = tupletInfo.onTime + tupletInfo.len;
const auto tupletNoteLen = tupletInfo.len / tupletInfo.tupletNumber;
ReducedFraction sumLen = {0, 1};
const auto opers = preferences.midiImportOperations.currentTrackOperations();
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
for (const auto &chord: tupletInfo.chords) {
const auto staccatoIt = (opers.simplifyDurations)
const auto staccatoIt = (opers.simplifyDurations.value(currentTrack))
? tupletInfo.staccatoChords.find(chord.first)
: tupletInfo.staccatoChords.end();
@ -320,8 +323,10 @@ std::vector<TupletInfo> detectTuplets(
auto tupletInfo = findTupletApproximation(divLen, tupletNumber,
basicQuant, startDivTime, startDivChordIt, endDivChordIt);
const auto opers = preferences.midiImportOperations.currentTrackOperations();
if (opers.simplifyDurations) {
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
if (opers.simplifyDurations.value(currentTrack)) {
if (!haveChordsInTheMiddleBetweenTupletChords(
startDivChordIt, endDivChordIt, tupletInfo)) {
detectStaccato(tupletInfo);

View file

@ -4,6 +4,7 @@
#include "importmidi_quant.h"
#include "importmidi_inner.h"
#include "importmidi_voice.h"
#include "importmidi_operations.h"
#include "libmscore/mscore.h"
#include "preferences.h"
@ -15,8 +16,9 @@ namespace MidiTuplet {
int tupletVoiceLimit()
{
const auto operations = preferences.midiImportOperations.currentTrackOperations();
const int allowedVoices = MidiVoice::toIntVoices(operations.allowedVoices);
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
const int allowedVoices = MidiVoice::toIntVoiceCount(opers.maxVoiceCount.value(currentTrack));
Q_ASSERT_X(allowedVoices <= VOICES,
"MidiTuplet::tupletVoiceLimit",

449
mscore/importmidi_view.cpp Normal file
View file

@ -0,0 +1,449 @@
#include "importmidi_view.h"
class SeparatorDelegate : public QStyledItemDelegate
{
public:
SeparatorDelegate(QObject *parent = 0)
: QStyledItemDelegate(parent)
, _frozenRowIndex(0)
, _frozenColIndex(0)
{}
void setFrozenRowIndex(int index)
{
_frozenRowIndex = index;
}
void setFrozenColIndex(int index)
{
_frozenColIndex = index;
}
void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
this->QStyledItemDelegate::paint(painter, option, index);
if (index.row() == _frozenRowIndex) {
painter->save();
painter->setPen(option.palette.foreground().color());
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
painter->restore();
}
if (index.column() == _frozenColIndex) {
painter->save();
painter->setPen(option.palette.foreground().color());
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
painter->restore();
}
}
private:
int _frozenRowIndex;
int _frozenColIndex;
};
//----------------------------------------------------------------------------------
TracksView::TracksView(QWidget *parent)
: QTableView(parent)
, _frozenRowCount(0)
, _frozenColCount(0)
{
_frozenHTableView = new QTableView(this);
_frozenVTableView = new QTableView(this);
_frozenCornerTableView = new QTableView(this);
_delegate = new SeparatorDelegate(this);
_delegate->setFrozenRowIndex(_frozenRowCount - 1);
_delegate->setFrozenColIndex(_frozenColCount - 1);
initHorizontalView();
initVerticalView();
initCornerView();
initMainView();
initConnections();
}
TracksView::~TracksView()
{
}
void TracksView::initHorizontalView()
{
_frozenHTableView->horizontalHeader()->hide();
_frozenHTableView->setSelectionBehavior(SelectItems);
_frozenHTableView->setSelectionMode(SingleSelection);
_frozenHTableView->setAutoScroll(false);
_frozenHTableView->setFocusPolicy(Qt::StrongFocus);
_frozenHTableView->setStyleSheet("QTableView { border: none; }");
_frozenHTableView->setItemDelegate(_delegate);
_frozenHTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_frozenHTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_frozenHTableView->show();
_frozenHTableView->setHorizontalScrollMode(ScrollPerPixel);
}
void TracksView::initVerticalView()
{
_frozenVTableView->verticalHeader()->hide();
_frozenVTableView->setSelectionBehavior(SelectItems);
_frozenVTableView->setSelectionMode(SingleSelection);
_frozenVTableView->setAutoScroll(false);
_frozenVTableView->setFocusPolicy(Qt::StrongFocus);
_frozenVTableView->setStyleSheet("QTableView { border: none; }");
_frozenVTableView->setItemDelegate(_delegate);
_frozenVTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_frozenVTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_frozenVTableView->show();
_frozenVTableView->setVerticalScrollMode(ScrollPerPixel);
_frozenHTableView->stackUnder(_frozenVTableView);
}
void TracksView::initCornerView()
{
_frozenCornerTableView->horizontalHeader()->hide();
_frozenCornerTableView->verticalHeader()->hide();
_frozenCornerTableView->setAutoScroll(false);
_frozenCornerTableView->setSelectionBehavior(SelectItems);
_frozenCornerTableView->setSelectionMode(SingleSelection);
_frozenCornerTableView->setFocusPolicy(Qt::StrongFocus);
_frozenCornerTableView->setStyleSheet("QTableView { border: none; }");
_frozenCornerTableView->setItemDelegate(_delegate);
_frozenCornerTableView->horizontalScrollBar()->setDisabled(true);
_frozenCornerTableView->verticalScrollBar()->setDisabled(true);
_frozenCornerTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_frozenCornerTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_frozenCornerTableView->show();
_frozenVTableView->stackUnder(_frozenCornerTableView);
}
void TracksView::initMainView()
{
horizontalHeader()->setSectionsMovable(true);
verticalHeader()->setSectionsMovable(true);
setHorizontalScrollMode(ScrollPerPixel);
setVerticalScrollMode(ScrollPerPixel);
setSelectionBehavior(SelectItems);
setSelectionMode(SingleSelection);
setAutoScroll(false);
setFocusPolicy(Qt::StrongFocus);
horizontalScrollBar()->setFocusPolicy(Qt::NoFocus);
verticalScrollBar()->setFocusPolicy(Qt::NoFocus);
viewport()->stackUnder(_frozenHTableView);
}
void TracksView::initConnections()
{
connect(horizontalHeader(),SIGNAL(sectionResized(int,int,int)),
this, SLOT(updateMainViewSectionWidth(int,int,int)));
connect(verticalHeader(),SIGNAL(sectionResized(int,int,int)),
this, SLOT(updateMainViewSectionHeight(int,int,int)));
connect(verticalHeader(), SIGNAL(sectionMoved(int,int,int)),
SLOT(onVSectionMove(int,int,int)));
connect(horizontalHeader(), SIGNAL(sectionMoved(int,int,int)),
SLOT(onHSectionMove(int,int,int)));
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
_frozenHTableView->horizontalScrollBar(), SLOT(setValue(int)));
connect(_frozenHTableView->horizontalScrollBar(), SIGNAL(valueChanged(int)),
horizontalScrollBar(), SLOT(setValue(int)));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
_frozenVTableView->verticalScrollBar(), SLOT(setValue(int)));
connect(_frozenVTableView->verticalScrollBar(), SIGNAL(valueChanged(int)),
verticalScrollBar(), SLOT(setValue(int)));
}
void TracksView::setModel(QAbstractItemModel *model)
{
QTableView::setModel(model);
_frozenHTableView->setModel(model);
_frozenVTableView->setModel(model);
_frozenCornerTableView->setModel(model);
_frozenHTableView->setSelectionModel(selectionModel());
_frozenVTableView->setSelectionModel(selectionModel());
_frozenCornerTableView->setSelectionModel(selectionModel());
connect(_frozenVTableView->selectionModel(),
SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
SLOT(currentChanged(const QModelIndex &, const QModelIndex &)));
connect(_frozenHTableView->selectionModel(),
SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
SLOT(currentChanged(const QModelIndex &, const QModelIndex &)));
connect(_frozenCornerTableView->selectionModel(),
SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
SLOT(currentChanged(const QModelIndex &, const QModelIndex &)));
setFrozenRowCount(_frozenRowCount);
setFrozenColCount(_frozenColCount);
connect(_frozenVTableView->horizontalHeader(),SIGNAL(sectionResized(int,int,int)),
this, SLOT(updateFrozenSectionWidth(int,int,int)), Qt::UniqueConnection);
connect(_frozenHTableView->verticalHeader(),SIGNAL(sectionResized(int,int,int)),
this, SLOT(updateFrozenSectionHeight(int,int,int)), Qt::UniqueConnection);
updateFrozenTableGeometry();
}
void TracksView::setFrozenRowCount(int count)
{
if (model()->rowCount() == 0)
return;
Q_ASSERT_X(count >= 0 && count < model()->rowCount(),
"TracksView::setFrozenRowCount", "Invalid frozen row count");
_frozenRowCount = count;
_delegate->setFrozenRowIndex(_frozenRowCount - 1);
for (int row = 0; row != count; ++row) {
_frozenHTableView->setRowHidden(row, false);
_frozenHTableView->setRowHeight(row, rowHeight(row));
}
for (int row = count; row < model()->rowCount(); ++row) {
_frozenHTableView->setRowHidden(row, true);
}
}
void TracksView::setFrozenColCount(int count)
{
if (model()->columnCount() == 0)
return;
Q_ASSERT_X(count >= 0 && count < model()->columnCount(),
"TracksView::setFrozenColCount", "Invalid frozen column count");
_frozenColCount = count;
_delegate->setFrozenColIndex(_frozenColCount - 1);
for (int col = 0; col != count; ++col) {
_frozenVTableView->setColumnHidden(col, false);
_frozenVTableView->setColumnWidth(col, columnWidth(col));
}
for (int col = count; col < model()->columnCount(); ++col) {
_frozenVTableView->setColumnHidden(col, true);
}
}
void TracksView::updateMainViewSectionWidth(int logicalIndex, int /*oldSize*/, int newSize)
{
bool changed = false;
for (int col = 0; col != _frozenColCount; ++col) {
if (logicalIndex == col) {
changed = true;
_frozenVTableView->setColumnWidth(col, newSize);
_frozenCornerTableView->setColumnWidth(col, newSize);
}
}
if (changed)
updateFrozenTableGeometry();
_frozenHTableView->setColumnWidth(logicalIndex, newSize);
}
void TracksView::updateMainViewSectionHeight(int logicalIndex, int /*oldSize*/, int newSize)
{
bool changed = false;
for (int row = 0; row != _frozenRowCount; ++row) {
if (logicalIndex == row) {
changed = true;
_frozenHTableView->setRowHeight(row, newSize);
_frozenCornerTableView->setRowHeight(row, newSize);
}
}
if (changed)
updateFrozenTableGeometry();
_frozenVTableView->setRowHeight(logicalIndex, newSize);
}
void TracksView::updateFrozenSectionWidth(int logicalIndex, int /*oldSize*/, int newSize)
{
bool changed = false;
for (int col = 0; col != _frozenColCount; ++col) {
if (logicalIndex == col) {
changed = true;
setColumnWidth(col, newSize);
_frozenCornerTableView->setColumnWidth(col, newSize);
}
}
if (changed)
updateFrozenTableGeometry();
_frozenHTableView->setColumnWidth(logicalIndex, newSize);
}
void TracksView::updateFrozenSectionHeight(int logicalIndex, int /*oldSize*/, int newSize)
{
bool changed = false;
for (int row = 0; row != _frozenRowCount; ++row) {
if (logicalIndex == row) {
changed = true;
setRowHeight(row, newSize);
_frozenCornerTableView->setRowHeight(row, newSize);
}
}
if (changed)
updateFrozenTableGeometry();
_frozenVTableView->setRowHeight(logicalIndex, newSize);
}
void TracksView::resizeEvent(QResizeEvent *event)
{
QTableView::resizeEvent(event);
updateFrozenTableGeometry();
}
void TracksView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
updateFocus(current.row(), current.column());
keepVisible(previous, current);
}
void TracksView::onHSectionMove(int /*logicalIndex*/, int oldVisualIndex, int newVisualIndex)
{
if (newVisualIndex < _frozenColCount) { // disallow move
horizontalHeader()->moveSection(newVisualIndex, oldVisualIndex); // move back
}
else if (oldVisualIndex >= _frozenColCount) {
// move only if this slot was not called after previous move back
_frozenHTableView->horizontalHeader()->moveSection(oldVisualIndex, newVisualIndex);
_frozenVTableView->horizontalHeader()->moveSection(oldVisualIndex, newVisualIndex);
_frozenCornerTableView->horizontalHeader()->moveSection(oldVisualIndex, newVisualIndex);
}
}
void TracksView::onVSectionMove(int /*logicalIndex*/, int oldVisualIndex, int newVisualIndex)
{
if (newVisualIndex < _frozenRowCount) { // disallow move
verticalHeader()->moveSection(newVisualIndex, oldVisualIndex); // move back
}
else if (oldVisualIndex >= _frozenRowCount) {
// move only if this slot was not called after previous move back
_frozenHTableView->verticalHeader()->moveSection(oldVisualIndex, newVisualIndex);
_frozenVTableView->verticalHeader()->moveSection(oldVisualIndex, newVisualIndex);
_frozenCornerTableView->verticalHeader()->moveSection(oldVisualIndex, newVisualIndex);
}
}
void TracksView::updateFocus(int currentRow, int currentColumn)
{
if (currentRow < _frozenRowCount) {
if (currentColumn < _frozenColCount)
_frozenCornerTableView->setFocus();
else
_frozenHTableView->setFocus();
}
else {
if (currentColumn < _frozenColCount)
_frozenVTableView->setFocus();
else
setFocus();
}
}
void TracksView::keepVisible(const QModelIndex &previous, const QModelIndex &current)
{
// update scroll bars to show the cell hidden by the frozen table view if it gets focus
const int hInvisibleGap = visualRect(current).topLeft().x() - frozenVTableWidth();
const int vInvisibleGap = visualRect(current).topLeft().y() - frozenHTableHeight();
if (current.column() != previous.column() && current.column() >= _frozenColCount
&& hInvisibleGap < 0) {
const int newValue = horizontalScrollBar()->value() + hInvisibleGap;
horizontalScrollBar()->setValue(newValue);
}
if (current.row() != previous.row() && current.row() >= _frozenRowCount
&& vInvisibleGap < 0) {
const int newValue = verticalScrollBar()->value() + vInvisibleGap;
verticalScrollBar()->setValue(newValue);
}
// update scroll bars to show the cell hidden by the borders of main table view
const int hInvisibleGap2 = width() - verticalHeader()->width() - 4 * frameWidth()
- ((verticalScrollBar()->isHidden()) ? 0 : verticalScrollBar()->width())
- visualRect(current).bottomRight().x();
const int vInvisibleGap2 = height() - horizontalHeader()->height() - 4 * frameWidth()
- ((horizontalScrollBar()->isHidden()) ? 0 : horizontalScrollBar()->height())
- visualRect(current).bottomRight().y();
if (current.column() != previous.column() && hInvisibleGap2 < 0) {
const int newValue = horizontalScrollBar()->value() - hInvisibleGap2;
horizontalScrollBar()->setValue(newValue);
}
if (current.row() != previous.row() && vInvisibleGap2 < 0) {
const int newValue = verticalScrollBar()->value() - vInvisibleGap2;
verticalScrollBar()->setValue(newValue);
}
}
int TracksView::frozenRowHeight()
{
int height = 0;
for (int row = 0; row != _frozenRowCount; ++row)
height += rowHeight(row);
return height;
}
int TracksView::frozenColWidth()
{
int width = 0;
for (int col = 0; col != _frozenColCount; ++col)
width += columnWidth(col);
return width;
}
int TracksView::frozenVTableWidth()
{
int width = 0;
for (int col = 0; col != _frozenColCount; ++col)
width += _frozenVTableView->columnWidth(col);
return width;
}
int TracksView::frozenHTableHeight()
{
int height = 0;
for (int row = 0; row != _frozenRowCount; ++row)
height += _frozenHTableView->rowHeight(row);
return height;
}
void TracksView::updateFrozenTableGeometry()
{
_frozenHTableView->setGeometry(
frameWidth(),
horizontalHeader()->height() + frameWidth(),
viewport()->width() + verticalHeader()->width(),
frozenRowHeight());
_frozenVTableView->setGeometry(
verticalHeader()->width() + frameWidth(),
frameWidth(),
frozenColWidth(),
viewport()->height() + horizontalHeader()->height());
_frozenCornerTableView->setGeometry(
verticalHeader()->width() + frameWidth(),
horizontalHeader()->height() + frameWidth(),
frozenColWidth(),
frozenRowHeight());
}

62
mscore/importmidi_view.h Normal file
View file

@ -0,0 +1,62 @@
#ifndef IMPORTMIDI_VIEW_H
#define IMPORTMIDI_VIEW_H
#include <QTableView>
class SeparatorDelegate;
class TracksView : public QTableView
{
Q_OBJECT
public:
TracksView(QWidget *parent);
~TracksView();
void setModel(QAbstractItemModel *model);
void setFrozenRowCount(int count);
void setFrozenColCount(int count);
protected:
void resizeEvent(QResizeEvent *event);
private slots:
void currentChanged(const QModelIndex &, const QModelIndex &);
void updateMainViewSectionWidth(int,int,int);
void updateMainViewSectionHeight(int,int,int);
void updateFrozenSectionWidth(int,int,int);
void updateFrozenSectionHeight(int,int,int);
void onHSectionMove(int,int,int);
void onVSectionMove(int,int,int);
private:
void initHorizontalView();
void initVerticalView();
void initCornerView();
void initMainView();
void initConnections();
void keepVisible(const QModelIndex &previous, const QModelIndex &current);
int frozenRowHeight();
int frozenColWidth();
int frozenVTableWidth();
int frozenHTableHeight();
void updateFrozenTableGeometry();
void updateFocus(int currentRow, int currentColumn);
QTableView *_frozenHTableView;
QTableView *_frozenVTableView;
QTableView *_frozenCornerTableView;
SeparatorDelegate *_delegate;
int _frozenRowCount;
int _frozenColCount;
};
#endif // IMPORTMIDI_VIEW_H

View file

@ -4,6 +4,7 @@
#include "importmidi_inner.h"
#include "importmidi_chord.h"
#include "importmidi_meter.h"
#include "importmidi_operations.h"
#include "libmscore/sig.h"
#include "libmscore/mscore.h"
#include "preferences.h"
@ -15,16 +16,16 @@ namespace MidiVoice {
// no more than VOICES
int toIntVoices(MidiOperation::AllowedVoices value)
int toIntVoiceCount(MidiOperations::VoiceCount value)
{
switch (value) {
case MidiOperation::AllowedVoices::V_1:
case MidiOperations::VoiceCount::V_1:
return 1;
case MidiOperation::AllowedVoices::V_2:
case MidiOperations::VoiceCount::V_2:
return 2;
case MidiOperation::AllowedVoices::V_3:
case MidiOperations::VoiceCount::V_3:
return 3;
case MidiOperation::AllowedVoices::V_4:
case MidiOperations::VoiceCount::V_4:
return 4;
}
return VOICES;
@ -32,14 +33,15 @@ int toIntVoices(MidiOperation::AllowedVoices value)
int voiceLimit()
{
const auto operations = preferences.midiImportOperations.currentTrackOperations();
const int allowedVoices = toIntVoices(operations.allowedVoices);
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
const int allowedVoiceCount = toIntVoiceCount(opers.maxVoiceCount.value(currentTrack));
Q_ASSERT_X(allowedVoices <= VOICES,
Q_ASSERT_X(allowedVoiceCount <= VOICES,
"MidiVoice::voiceLimit",
"Allowed voice count exceeds MuseScore voice limit");
return allowedVoices;
return allowedVoiceCount;
}
@ -120,8 +122,9 @@ int findDurationCountInGroup(
"MidiVoice::findDurationCountInGroup",
"Notes are not sorted by off time in ascending order");
const auto &opers = preferences.midiImportOperations.currentTrackOperations();
const bool useDots = opers.useDots;
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
const bool useDots = opers.useDots.value(currentTrack);
int count = 0;
auto onTime = chordOnTime;
@ -790,7 +793,7 @@ void sortVoices(
const auto &chord = it->second;
// some notes: if chord off time belongs to tuplet
// then this tuplet belongs to the same bar as the chord off time
// then this tuplet belongs to the same bar as the chord off time;
// same is for chord on time
if (chord.barIndex <= maxBarIndex) {
@ -823,11 +826,13 @@ bool separateVoices(std::multimap<int, MTrack> &tracks, const TimeSigMap *sigmap
auto &chords = track.second.chords;
if (chords.empty())
continue;
const int userVoiceCount = toIntVoiceCount(
opers.data()->trackOpers.maxVoiceCount.value(mtrack.indexOfOperation));
// pass current track index through MidiImportOperations
// for further usage
opers.setCurrentTrack(mtrack.indexOfOperation);
MidiOperations::CurrentTrackSetter setCurrentTrack{opers, mtrack.indexOfOperation};
if (opers.currentTrackOperations().separateVoices && voiceLimit() > 1) {
if (userVoiceCount > 1 && userVoiceCount <= voiceLimit()) {
Q_ASSERT_X(MidiTuplet::areAllTupletsReferenced(mtrack.chords, mtrack.tuplets),
"MidiVoice::separateVoices",

View file

@ -11,7 +11,7 @@ class TimeSigMap;
namespace MidiVoice {
int toIntVoices(MidiOperation::AllowedVoices value);
int toIntVoiceCount(MidiOperations::VoiceCount value);
int voiceLimit();
bool separateVoices(std::multimap<int, MTrack> &tracks, const TimeSigMap *sigmap);

View file

@ -1603,12 +1603,12 @@ void MuseScore::midiPanelOnSwitchToFile(const QString &file)
bool isMidiFile = ImportMidiPanel::isMidiFile(file);
if (isMidiFile) {
importmidiPanel->setMidiFile(file);
if (importmidiPanel->prefferedVisible())
if (importmidiPanel->isPrefferedVisible())
importmidiPanel->setVisible(true);
}
else
importmidiPanel->setVisible(false);
importmidiShowPanel->setVisible(!importmidiPanel->prefferedVisible() && isMidiFile);
importmidiShowPanel->setVisible(!importmidiPanel->isPrefferedVisible() && isMidiFile);
}
void MuseScore::midiPanelOnCloseFile(const QString &file)
@ -1623,10 +1623,10 @@ void MuseScore::allowShowMidiPanel(const QString &file)
importmidiPanel->setPrefferedVisible(true);
}
void MuseScore::setMidiPrefOperations(const QString &file)
void MuseScore::setReopenInProgress(const QString &file)
{
if (ImportMidiPanel::isMidiFile(file))
importmidiPanel->setMidiPrefOperations(file);
importmidiPanel->setReopenInProgress();
}
void MuseScore::showMidiImportPanel()

View file

@ -620,7 +620,7 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
void midiPanelOnSwitchToFile(const QString &file);
void midiPanelOnCloseFile(const QString &file);
void allowShowMidiPanel(const QString &file);
void setMidiPrefOperations(const QString &file);
void setReopenInProgress(const QString &file);
static Palette* newTempoPalette();
static Palette* newTextPalette();

View file

@ -147,7 +147,7 @@ struct Preferences {
QString importCharsetGP;
QString importStyleFile;
int shortestNote; // for midi input
MidiImportOperations midiImportOperations;
MidiOperations::Data midiImportOperations;
bool useOsc;
int oscPort;

View file

@ -51,7 +51,6 @@ add_library(
${PROJECT_SOURCE_DIR}/mscore/importmidi_tuplet_voice.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_tuplet_tonotes.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_chord.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_data.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_fraction.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_swing.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_drum.cpp

View file

@ -31,6 +31,7 @@
#include "mscore/importmidi_inner.h"
#include "mscore/importmidi_quant.h"
#include "mscore/importmidi_fraction.h"
#include "mscore/importmidi_operations.h"
#include "mscore/preferences.h"
@ -50,52 +51,54 @@ class TestImportMidi : public QObject, public MTest
{
Q_OBJECT
void mf(const char* name);
QString midiFilePath(const QString &fileName) const;
QString midiFilePath(const char* fileName) const;
void mf(const char* name) const;
// functions that modify default settings
void dontSimplify(const char *file)
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.simplifyDurations = false;
opers.separateVoices = false;
opers.LHRH.doIt = false;
preferences.midiImportOperations.resetDefaults(opers);
preferences.midiImportOperations.addNewFile(midiFilePath(file));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.simplifyDurations.setDefaultValue(false);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_1);
data.trackOpers.doStaffSplit.setDefaultValue(false);
mf(file);
preferences.midiImportOperations.clear();
}
void voiceSeparation(const char *file, bool simplify = false)
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.LHRH.doIt = false;
opers.simplifyDurations = simplify;
opers.separateVoices = true;
preferences.midiImportOperations.resetDefaults(opers);
preferences.midiImportOperations.addNewFile(midiFilePath(file));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.doStaffSplit.setDefaultValue(false);
data.trackOpers.simplifyDurations.setDefaultValue(simplify);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_4);
mf(file);
preferences.midiImportOperations.clear();
}
void simplification(const char *file)
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.LHRH.doIt = false;
opers.simplifyDurations = true;
opers.separateVoices = false;
preferences.midiImportOperations.resetDefaults(opers);
preferences.midiImportOperations.addNewFile(midiFilePath(file));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.doStaffSplit.setDefaultValue(false);
data.trackOpers.simplifyDurations.setDefaultValue(true);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_1);
mf(file);
preferences.midiImportOperations.clear();
}
void lrhand(const char *file)
void staffSplit(const char *file)
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.LHRH.doIt = true;
opers.simplifyDurations = false;
opers.separateVoices = false;
preferences.midiImportOperations.resetDefaults(opers);
preferences.midiImportOperations.addNewFile(midiFilePath(file));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.doStaffSplit.setDefaultValue(true);
data.trackOpers.simplifyDurations.setDefaultValue(false);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_1);
mf(file);
preferences.midiImportOperations.clear();
}
private slots:
@ -119,13 +122,15 @@ class TestImportMidi : public QObject, public MTest
// human-performed (unaligned) files
void human4_4()
{
TrackOperations opers;
opers.simplifyDurations = false;
opers.separateVoices = false;
opers.LHRH.doIt = false;
preferences.midiImportOperations.resetDefaults(opers);
mf("human_4-4");
preferences.midiImportOperations.clear();
QString midiFile("human_4-4");
preferences.midiImportOperations.addNewFile(midiFilePath(midiFile));
preferences.midiImportOperations.addNewFile(midiFile);
auto &data = *preferences.midiImportOperations.data();
data.trackOpers.doStaffSplit.setDefaultValue(false);
data.trackOpers.simplifyDurations.setDefaultValue(false);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_1);
mf(midiFile.toStdString().c_str());
}
// chord detection
@ -179,15 +184,15 @@ class TestImportMidi : public QObject, public MTest
void tuplet2VoicesTupletNon() { mf("tuplet_2_voices_tuplet_non"); }
void tuplet3_5_7tuplets()
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.changeClef = false;
opers.simplifyDurations = false;
opers.separateVoices = false;
opers.LHRH.doIt = false;
preferences.midiImportOperations.resetDefaults(opers);
mf("tuplet_3_5_7_tuplets");
preferences.midiImportOperations.clear();
QString midiFile("tuplet_3_5_7_tuplets");
preferences.midiImportOperations.addNewFile(midiFilePath(midiFile));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.changeClef.setDefaultValue(false);
data.trackOpers.doStaffSplit.setDefaultValue(false);
data.trackOpers.simplifyDurations.setDefaultValue(false);
mf(midiFile.toStdString().c_str());
}
void tuplet5_5TupletsRests() { dontSimplify("tuplet_5_5_tuplets_rests"); }
void tuplet3_4() { dontSimplify("tuplet_3-4"); }
@ -237,54 +242,57 @@ class TestImportMidi : public QObject, public MTest
void pickupMeasure() { dontSimplify("pickup"); }
// LH/RH separation
void LHRH_Nontuplet() { lrhand("split_nontuplet"); }
void LHRH_Acid() { lrhand("split_acid"); }
void LHRH_Tuplet() { lrhand("split_tuplet"); }
void LHRH_2melodies() { lrhand("split_2_melodies"); }
void LHRH_octave() { lrhand("split_octave"); }
void LHRH_Nontuplet() { staffSplit("split_nontuplet"); }
void LHRH_Acid() { staffSplit("split_acid"); }
void LHRH_Tuplet() { staffSplit("split_tuplet"); }
void LHRH_2melodies() { staffSplit("split_2_melodies"); }
void LHRH_octave() { staffSplit("split_octave"); }
// swing
void swingTriplets()
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.swing = MidiOperation::Swing::SWING;
opers.simplifyDurations = false;
opers.separateVoices = false;
opers.LHRH.doIt = false;
preferences.midiImportOperations.resetDefaults(opers);
mf("swing_triplets");
preferences.midiImportOperations.clear();
QString midiFile("swing_triplets");
preferences.midiImportOperations.addNewFile(midiFilePath(midiFile));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.swing.setDefaultValue(MidiOperations::Swing::SWING);
data.trackOpers.doStaffSplit.setDefaultValue(false);
data.trackOpers.simplifyDurations.setDefaultValue(false);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_1);
mf(midiFile.toStdString().c_str());
}
void swingShuffle()
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.swing = MidiOperation::Swing::SHUFFLE;
opers.simplifyDurations = false;
opers.separateVoices = false;
opers.LHRH.doIt = false;
preferences.midiImportOperations.resetDefaults(opers);
mf("swing_shuffle");
preferences.midiImportOperations.clear();
QString midiFile("swing_shuffle");
preferences.midiImportOperations.addNewFile(midiFilePath(midiFile));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.swing.setDefaultValue(MidiOperations::Swing::SHUFFLE);
data.trackOpers.doStaffSplit.setDefaultValue(false);
data.trackOpers.simplifyDurations.setDefaultValue(false);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_1);
mf(midiFile.toStdString().c_str());
}
void swingClef()
{
TrackOperations opers;
opers.canRedefineDefaultsLater = false;
opers.swing = MidiOperation::Swing::SWING;
opers.changeClef = true;
opers.simplifyDurations = false;
opers.separateVoices = false;
opers.LHRH.doIt = false;
preferences.midiImportOperations.resetDefaults(opers);
mf("swing_clef");
preferences.midiImportOperations.clear();
QString midiFile("swing_clef");
preferences.midiImportOperations.addNewFile(midiFilePath(midiFile));
auto &data = *preferences.midiImportOperations.data();
data.canRedefineDefaultsLater = false;
data.trackOpers.swing.setDefaultValue(MidiOperations::Swing::SWING);
data.trackOpers.changeClef.setDefaultValue(true);
data.trackOpers.doStaffSplit.setDefaultValue(false);
data.trackOpers.simplifyDurations.setDefaultValue(false);
data.trackOpers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_1);
mf(midiFile.toStdString().c_str());
}
// percussion
void percDrums() { dontSimplify("perc_drums"); }
void percRemoveTies() { dontSimplify("perc_remove_ties"); }
void percDrums() { mf("perc_drums"); }
void percRemoveTies() { mf("perc_remove_ties"); }
// clef changes along the score
void clefTied() { dontSimplify("clef_tied"); }
@ -319,17 +327,27 @@ void TestImportMidi::initTestCase()
// midifile
//---------------------------------------------------------
void TestImportMidi::mf(const char* name)
void TestImportMidi::mf(const char* name) const
{
Score* score = new Score(mscore->baseStyle());
score->setName(name);
const QString midiname = QString(name) + ".mid";
const QString mscorename = QString(name) + ".mscx";
QCOMPARE(importMidi(score, TESTROOT "/mtest/" + DIR + midiname), Score::FileError::FILE_NO_ERROR);
QCOMPARE(importMidi(score, midiFilePath(name)), Score::FileError::FILE_NO_ERROR);;
QVERIFY(saveCompareScore(score, mscorename, DIR + mscorename));
delete score;
}
QString TestImportMidi::midiFilePath(const QString &fileName) const
{
const QString nameWithExtention = fileName + ".mid";
return QString(TESTROOT "/mtest/" + DIR + nameWithExtention);
}
QString TestImportMidi::midiFilePath(const char* fileName) const
{
return midiFilePath(QString(fileName));
}
//---------------------------------------------------------
// tuplet recognition fuctions
//---------------------------------------------------------
@ -532,6 +550,9 @@ void TestImportMidi::isTupletAllowed()
void TestImportMidi::findTupletNumbers()
{
auto &opers = preferences.midiImportOperations;
opers.addNewFile("");
MidiOperations::CurrentTrackSetter setCurrentTrack{opers, 0};
{
const ReducedFraction barFraction(4, 4);
const ReducedFraction divLen = barFraction / 4;

View file

@ -23,6 +23,7 @@
#include "synthesizer/msynthesizer.h"
#include "mscore/musescoreCore.h"
#include "mscore/shortcut.h"
#include "mscore/importmidi_operations.h"
#include "libmscore/xml.h"
#include "libmscore/excerpt.h"
@ -160,7 +161,7 @@ Score* MTest::readCreatedScore(const QString& name)
// saveScore
//---------------------------------------------------------
bool MTest::saveScore(Score* score, const QString& name)
bool MTest::saveScore(Score* score, const QString& name) const
{
QFileInfo fi(name);
// MScore::testMode = true;
@ -171,7 +172,7 @@ bool MTest::saveScore(Score* score, const QString& name)
// compareFiles
//---------------------------------------------------------
bool MTest::compareFiles(const QString& saveName, const QString& compareWith)
bool MTest::compareFiles(const QString& saveName, const QString& compareWith) const
{
QString cmd = "diff";
QStringList args;
@ -196,7 +197,7 @@ bool MTest::compareFiles(const QString& saveName, const QString& compareWith)
// saveCompareScore
//---------------------------------------------------------
bool MTest::saveCompareScore(Score* score, const QString& saveName, const QString& compareWith)
bool MTest::saveCompareScore(Score* score, const QString& saveName, const QString& compareWith) const
{
saveScore(score, saveName);
return compareFiles(saveName, compareWith);

View file

@ -33,11 +33,11 @@ class MTest {
MTest();
Ms::Score* readScore(const QString& name);
Ms::Score* readCreatedScore(const QString& name);
bool saveScore(Ms::Score*, const QString& name);
bool saveScore(Ms::Score*, const QString& name) const;
bool savePdf(Ms::Score*, const QString& name);
bool saveMusicXml(Ms::Score*, const QString& name);
bool compareFiles(const QString& saveName, const QString& compareWith);
bool saveCompareScore(Ms::Score*, const QString& saveName, const QString& compareWith);
bool compareFiles(const QString& saveName, const QString& compareWith) const;
bool saveCompareScore(Ms::Score*, const QString& saveName, const QString& compareWith) const;
bool saveCompareMusicXmlScore(Ms::Score*, const QString& saveName, const QString& compareWith);
Ms::Element* writeReadElement(Ms::Element* element);
void initMTest();