diff --git a/include/MidiClip.h b/include/MidiClip.h index f3150ba6f..5d1d7a789 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -79,6 +79,9 @@ public: Note * addStepNote( int step ); void setStep( int step, bool enabled ); + //! Horizontally flip the positions of the given notes. + void reverseNotes(const NoteVector& notes); + // Split the list of notes on the given position void splitNotes(const NoteVector& notes, TimePos pos); diff --git a/include/PianoRoll.h b/include/PianoRoll.h index fb175c374..00bdbaeb9 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -248,6 +248,7 @@ protected slots: void clearGhostClip(); void glueNotes(); void fitNoteLengths(bool fill); + void reverseNotes(); void constrainNoteLengths(bool constrainMax); void changeSnapMode(); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 63d2a81a6..9dba3e7f0 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -786,6 +786,20 @@ void PianoRoll::constrainNoteLengths(bool constrainMax) Engine::getSong()->setModified(); } +void PianoRoll::reverseNotes() +{ + if (!hasValidMidiClip()) { return; } + + const NoteVector selectedNotes = getSelectedNotes(); + const auto& notes = selectedNotes.empty() ? m_midiClip->notes() : selectedNotes; + + m_midiClip->reverseNotes(notes); + + update(); + getGUI()->songEditor()->update(); + Engine::getSong()->setModified(); +} + void PianoRoll::loadMarkedSemiTones(const QDomElement & de) { @@ -5035,6 +5049,10 @@ PianoRollWindow::PianoRollWindow() : auto maxLengthAction = new QAction(embed::getIconPixmap("max_length"), tr("Max length as last"), noteToolsButton); connect(maxLengthAction, &QAction::triggered, [this](){ m_editor->constrainNoteLengths(true); }); + auto reverseAction = new QAction(embed::getIconPixmap("flip_x"), tr("Reverse Notes"), noteToolsButton); + connect(reverseAction, &QAction::triggered, [this](){ m_editor->reverseNotes(); }); + reverseAction->setShortcut(combine(Qt::SHIFT, Qt::Key_R)); + noteToolsButton->addAction(glueAction); noteToolsButton->addAction(knifeAction); noteToolsButton->addAction(strumAction); @@ -5042,6 +5060,7 @@ PianoRollWindow::PianoRollWindow() : noteToolsButton->addAction(cutOverlapsAction); noteToolsButton->addAction(minLengthAction); noteToolsButton->addAction(maxLengthAction); + noteToolsButton->addAction(reverseAction); notesActionsToolBar->addWidget(noteToolsButton); diff --git a/src/tracks/MidiClip.cpp b/src/tracks/MidiClip.cpp index ab5321687..0d3e5a5b4 100644 --- a/src/tracks/MidiClip.cpp +++ b/src/tracks/MidiClip.cpp @@ -314,6 +314,25 @@ void MidiClip::setStep( int step, bool enabled ) +void MidiClip::reverseNotes(const NoteVector& notes) +{ + addJournalCheckPoint(); + + // Find the very first start position and the very last end position of all the notes. + TimePos firstPos = (*std::min_element(notes.begin(), notes.end(), [](const Note* n1, const Note* n2){ return Note::lessThan(n1, n2); }))->pos(); + TimePos lastPos = (*std::max_element(notes.begin(), notes.end(), [](const Note* n1, const Note* n2){ return n1->endPos() < n2->endPos(); }))->endPos(); + + for (auto note : notes) + { + TimePos newStart = lastPos - (note->pos() - firstPos) - note->length(); + note->setPos(newStart); + } + + rearrangeAllNotes(); + emit dataChanged(); +} + + void MidiClip::splitNotes(const NoteVector& notes, TimePos pos) {