Skip to content
Merged
Binary file added data/themes/default/cut_overlaps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/themes/default/fill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/themes/default/max_length.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data/themes/default/min_length.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions include/PianoRoll.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ protected slots:

void clearGhostPattern();
void glueNotes();
void fitNoteLengths(bool fill);
void constrainNoteLengths(bool constrainMax);


signals:
Expand Down
101 changes: 98 additions & 3 deletions src/gui/editors/PianoRoll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,86 @@ void PianoRoll::glueNotes()
}
}

void PianoRoll::fitNoteLengths(bool fill)
{
if (!hasValidPattern()) { return; }
m_pattern->addJournalCheckPoint();

// Reference notes
NoteVector refNotes = m_pattern->notes();
std::sort(refNotes.begin(), refNotes.end(), Note::lessThan);

// Notes to edit
NoteVector notes = getSelectedNotes();
if (notes.empty())
{
notes = refNotes;
}
else if (!fill)
{
std::sort(notes.begin(), notes.end(), Note::lessThan);
}
if (fill)
{
std::sort(notes.begin(), notes.end(), [](Note* n1, Note* n2) { return n1->endPos() < n2->endPos(); });
}

int length;
NoteVector::iterator ref = refNotes.begin();
for (Note* note : notes)
{
// Fast forward to next reference note
while (ref != refNotes.end() && (fill ? (*ref)->pos() < note->endPos() : (*ref)->pos() <= note->pos()))
{
ref++;
}
if (ref == refNotes.end())
{
if (!fill) { break; }
// Last notes stretch to end of last bar
length = notes.last()->endPos().nextFullBar() * TimePos::ticksPerBar() - note->pos();
}
else
{
length = (*ref)->pos() - note->pos();
}
if (fill ? note->length() < length : note->length() > length)
{
note->setLength(length);
}
}

update();
gui->songEditor()->update();
Engine::getSong()->setModified();
}


void PianoRoll::constrainNoteLengths(bool constrainMax)
{
if (!hasValidPattern()) { return; }
m_pattern->addJournalCheckPoint();

NoteVector notes = getSelectedNotes();
if (notes.empty())
{
notes = m_pattern->notes();
}

TimePos bound = m_lenOfNewNotes; // will be length of last note
for (Note* note : notes)
{
if (constrainMax ? note->length() > bound : note->length() < bound)
{
note->setLength(bound);
}
}

update();
gui->songEditor()->update();
Engine::getSong()->setModified();
}


void PianoRoll::loadMarkedSemiTones(const QDomElement & de)
{
Expand Down Expand Up @@ -4603,25 +4683,40 @@ PianoRollWindow::PianoRollWindow() :
m_editor->m_timeLine->addToolButtons( timeLineToolBar );

// -- Note modifier tools
// Toolbar
QToolButton * noteToolsButton = new QToolButton(m_toolBar);
noteToolsButton->setIcon(embed::getIconPixmap("tool"));
noteToolsButton->setPopupMode(QToolButton::InstantPopup);

// Glue
QAction * glueAction = new QAction(embed::getIconPixmap("glue"),
tr("Glue"), noteToolsButton);
connect(glueAction, SIGNAL(triggered()), m_editor, SLOT(glueNotes()));
glueAction->setShortcut( Qt::SHIFT | Qt::Key_G );

// Knife
QAction * knifeAction = new QAction(embed::getIconPixmap("edit_knife"),
tr("Knife"), noteToolsButton);
connect(knifeAction, &QAction::triggered, m_editor, &PianoRoll::setKnifeAction);
knifeAction->setShortcut( Qt::SHIFT | Qt::Key_K );

QAction* fillAction = new QAction(embed::getIconPixmap("fill"), tr("Fill"), noteToolsButton);
connect(fillAction, &QAction::triggered, [this](){ m_editor->fitNoteLengths(true); });
fillAction->setShortcut(Qt::SHIFT | Qt::Key_F);

QAction* cutOverlapsAction = new QAction(embed::getIconPixmap("cut_overlaps"), tr("Cut overlaps"), noteToolsButton);
connect(cutOverlapsAction, &QAction::triggered, [this](){ m_editor->fitNoteLengths(false); });
cutOverlapsAction->setShortcut(Qt::SHIFT | Qt::Key_C);

QAction* minLengthAction = new QAction(embed::getIconPixmap("min_length"), tr("Min length as last"), noteToolsButton);
connect(minLengthAction, &QAction::triggered, [this](){ m_editor->constrainNoteLengths(false); });

QAction* maxLengthAction = new QAction(embed::getIconPixmap("max_length"), tr("Max length as last"), noteToolsButton);
connect(maxLengthAction, &QAction::triggered, [this](){ m_editor->constrainNoteLengths(true); });

noteToolsButton->addAction(glueAction);
noteToolsButton->addAction(knifeAction);
noteToolsButton->addAction(fillAction);
noteToolsButton->addAction(cutOverlapsAction);
noteToolsButton->addAction(minLengthAction);
noteToolsButton->addAction(maxLengthAction);

notesActionsToolBar->addWidget(noteToolsButton);

Expand Down