Transpose midi clips in song editor (#6409)

* Transpose midi clips in song editor

* Fix undo stupidity

* Check boundries when transposing clips

and move transpose function to NoteVector

* Avoid update if nothing has changed

* Make getNoteBounds a separate function

* Rename getNoteBounds to boundsForNotes

* bool operator instead of optional + qobject_cast

* Revert "bool operator instead of optional + qobject_cast"

This reverts commit 98c56a96cf.

* qobject_cast and nullopt
This commit is contained in:
Alex
2022-07-04 03:16:22 +02:00
committed by GitHub
parent 29efb5d6ba
commit 9705c31773
4 changed files with 109 additions and 2 deletions

View File

@@ -73,6 +73,7 @@ protected slots:
void resetName();
void changeName();
void transposeSelection();
protected:

View File

@@ -26,6 +26,7 @@
#ifndef NOTE_H
#define NOTE_H
#include <optional>
#include <QVector>
#include "volume.h"
@@ -252,6 +253,18 @@ private:
typedef QVector<Note *> NoteVector;
struct NoteBounds
{
TimePos start;
TimePos end;
int lowest;
int highest;
};
std::optional<NoteBounds> boundsForNotes(const NoteVector& notes);
} // namespace lmms
#endif

View File

@@ -237,4 +237,33 @@ bool Note::withinRange(int tickStart, int tickEnd) const
}
/*! \brief Get the start/end/bottom/top positions of notes in a vector
*
* Returns no value if there are no notes
*/
std::optional<NoteBounds> boundsForNotes(const NoteVector& notes)
{
if (notes.empty()) { return std::nullopt; }
TimePos start = notes.front()->pos();
TimePos end = start;
int lower = notes.front()->key();
int upper = lower;
for (const Note* note: notes)
{
// TODO should we assume that NoteVector is always sorted correctly,
// so first() always has the lowest time position?
start = std::min(start, note->pos());
end = std::max(end, note->endPos());
lower = std::min(lower, note->key());
upper = std::max(upper, note->key());
}
return NoteBounds{start, end, lower, upper};
}
} // namespace lmms

View File

@@ -27,6 +27,7 @@
#include <cmath>
#include <QApplication>
#include <QInputDialog>
#include <QMenu>
#include <QPainter>
@@ -36,6 +37,7 @@
#include "MidiClip.h"
#include "PianoRoll.h"
#include "RenameDialog.h"
#include "TrackView.h"
namespace lmms::gui
{
@@ -144,8 +146,66 @@ void MidiClipView::changeName()
void MidiClipView::transposeSelection()
{
const auto selection = getClickedClips();
// Calculate the key boundries for all clips
int highest = 0;
int lowest = NumKeys - 1;
for (ClipView* clipview: selection)
{
if (auto mcv = qobject_cast<MidiClipView*>(clipview))
{
if (auto bounds = boundsForNotes(mcv->getMidiClip()->notes()))
{
lowest = std::min(bounds->lowest, lowest);
highest = std::max(bounds->highest, highest);
}
}
}
int semitones = QInputDialog::getInt(this, tr("Transpose"), tr("Semitones to transpose by:"),
/*start*/ 0, /*min*/ -lowest, /*max*/ (NumKeys - 1 - highest));
if (semitones == 0) { return; }
// TODO make this not crash
// Engine::getSong()->addJournalCheckPoint();
QSet<Track*> m_changedTracks;
for (ClipView* clipview: selection)
{
auto mcv = qobject_cast<MidiClipView*>(clipview);
if (!mcv) { continue; }
auto clip = mcv->getMidiClip();
if (clip->notes().empty()) { continue; }
auto track = clipview->getTrackView()->getTrack();
if (!m_changedTracks.contains(track))
{
track->addJournalCheckPoint();
m_changedTracks.insert(track);
}
for (Note* note: clip->notes())
{
note->setKey(note->key() + semitones);
}
emit clip->dataChanged();
}
// At least one clip must have notes to show the transpose dialog, so something *has* changed
Engine::getSong()->setModified();
}
void MidiClipView::constructContextMenu( QMenu * _cm )
{
bool isBeat = m_clip->type() == MidiClip::BeatClip;
QAction * a = new QAction( embed::getIconPixmap( "piano" ),
tr( "Open in piano-roll" ), _cm );
_cm->insertAction( _cm->actions()[0], a );
@@ -163,6 +223,10 @@ void MidiClipView::constructContextMenu( QMenu * _cm )
_cm->addAction( embed::getIconPixmap( "edit_erase" ),
tr( "Clear all notes" ), m_clip, SLOT(clear()));
if (!isBeat)
{
_cm->addAction(embed::getIconPixmap("scale"), tr("Transpose"), this, &MidiClipView::transposeSelection);
}
_cm->addSeparator();
_cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ),
@@ -171,7 +235,7 @@ void MidiClipView::constructContextMenu( QMenu * _cm )
tr( "Change name" ),
this, SLOT(changeName()));
if ( m_clip->type() == MidiClip::BeatClip )
if (isBeat)
{
_cm->addSeparator();
@@ -622,4 +686,4 @@ void MidiClipView::paintEvent( QPaintEvent * )
}
} // namespace lmms::gui
} // namespace lmms::gui