diff --git a/include/MidiClip.h b/include/MidiClip.h index b3ed0d84a..f3150ba6f 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -82,6 +82,9 @@ public: // Split the list of notes on the given position void splitNotes(const NoteVector& notes, TimePos pos); + // Split the list of notes along a line + void splitNotesAlongLine(const NoteVector notes, TimePos pos1, int key1, TimePos pos2, int key2, bool deleteShortEnds); + // clip-type stuff inline Type type() const { diff --git a/include/PianoRoll.h b/include/PianoRoll.h index e9939101c..a1d045ff4 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -456,9 +456,14 @@ private: // did we start a mouseclick with shift pressed bool m_startedWithShift; - // Variable that holds the position in ticks for the knife action - int m_knifeTickPos; - void updateKnifePos(QMouseEvent* me); + // Variables that hold the start and end position for the knife line + TimePos m_knifeStartTickPos; + int m_knifeStartKey; + TimePos m_knifeEndTickPos; + int m_knifeEndKey; + bool m_knifeDown; + + void updateKnifePos(QMouseEvent* me, bool initial); friend class PianoRollWindow; diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 41b760495..f0f54f0ba 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -1608,19 +1608,8 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) // -- Knife if (m_editMode == EditMode::Knife && me->button() == Qt::LeftButton) { - NoteVector n; - Note* note = noteUnderMouse(); - - if (note) - { - n.push_back(note); - - updateKnifePos(me); - - // Call splitNotes for the note - m_midiClip->splitNotes(n, TimePos(m_knifeTickPos)); - } - + updateKnifePos(me, true); + m_knifeDown = true; update(); return; } @@ -2138,6 +2127,7 @@ void PianoRoll::setKnifeAction() m_knifeMode = m_editMode; m_editMode = EditMode::Knife; m_action = Action::Knife; + m_knifeDown = false; setCursor(Qt::ArrowCursor); update(); } @@ -2147,6 +2137,7 @@ void PianoRoll::cancelKnifeAction() { m_editMode = m_knifeMode; m_action = Action::None; + m_knifeDown = false; update(); } @@ -2275,6 +2266,13 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) m_midiClip->rearrangeAllNotes(); } + else if (m_action == Action::Knife && hasValidMidiClip()) + { + bool deleteShortEnds = me->modifiers() & Qt::ShiftModifier; + const NoteVector selectedNotes = getSelectedNotes(); + m_midiClip->splitNotesAlongLine(!selectedNotes.empty() ? selectedNotes : m_midiClip->notes(), TimePos(m_knifeStartTickPos), m_knifeStartKey, TimePos(m_knifeEndTickPos), m_knifeEndKey, deleteShortEnds); + m_knifeDown = false; + } if( m_action == Action::MoveNote || m_action == Action::ResizeNote ) { @@ -2378,7 +2376,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) // Update Knife position if we are on knife mode if (m_editMode == EditMode::Knife) { - updateKnifePos(me); + updateKnifePos(me, false); } if( me->y() > PR_TOP_MARGIN || m_action != Action::None ) @@ -2759,19 +2757,27 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) -void PianoRoll::updateKnifePos(QMouseEvent* me) +void PianoRoll::updateKnifePos(QMouseEvent* me, bool initial) { // Calculate the TimePos from the mouse - int mouseViewportPos = me->x() - m_whiteKeyWidth; - int mouseTickPos = mouseViewportPos * TimePos::ticksPerBar() / m_ppb + m_currentPosition; + int mouseViewportPosX = me->x() - m_whiteKeyWidth; + int mouseViewportPosY = keyAreaBottom() - 1 - me->y(); + int mouseTickPos = mouseViewportPosX * TimePos::ticksPerBar() / m_ppb + m_currentPosition; + int mouseKey = std::round(1.f * mouseViewportPosY / m_keyLineHeight) + m_startKey - 1; // If ctrl is not pressed, quantize the position if (!(me->modifiers() & Qt::ControlModifier)) { - mouseTickPos = floor(mouseTickPos / quantization()) * quantization(); + mouseTickPos = std::round(1.f * mouseTickPos / quantization()) * quantization(); } - m_knifeTickPos = mouseTickPos; + if (initial) + { + m_knifeStartTickPos = mouseTickPos; + m_knifeStartKey = mouseKey; + } + m_knifeEndTickPos = mouseTickPos; + m_knifeEndKey = mouseKey; } @@ -3531,37 +3537,19 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } // -- Knife tool (draw cut line) - if (m_action == Action::Knife) + if (m_action == Action::Knife && m_knifeDown) { auto xCoordOfTick = [this](int tick) { return m_whiteKeyWidth + ( (tick - m_currentPosition) * m_ppb / TimePos::ticksPerBar()); }; - Note* n = noteUnderMouse(); - if (n) - { - const int key = n->key() - m_startKey + 1; - int y = y_base - key * m_keyLineHeight; + int x1 = xCoordOfTick(m_knifeStartTickPos); + int y1 = y_base - (m_knifeStartKey - m_startKey + 1) * m_keyLineHeight; + int x2 = xCoordOfTick(m_knifeEndTickPos); + int y2 = y_base - (m_knifeEndKey - m_startKey + 1) * m_keyLineHeight; - int x = xCoordOfTick(m_knifeTickPos); - - if (x > xCoordOfTick(n->pos()) && - x < xCoordOfTick(n->pos() + n->length())) - { - p.setPen(QPen(m_knifeCutLineColor, 1)); - p.drawLine(x, y, x, y + m_keyLineHeight); - - setCursor(Qt::BlankCursor); - } - else - { - setCursor(Qt::ArrowCursor); - } - } - else - { - setCursor(Qt::ArrowCursor); - } + p.setPen(QPen(m_knifeCutLineColor, 1)); + p.drawLine(x1, y1, x2, y2); } // -- End knife tool diff --git a/src/tracks/MidiClip.cpp b/src/tracks/MidiClip.cpp index 409fb60ae..ab5321687 100644 --- a/src/tracks/MidiClip.cpp +++ b/src/tracks/MidiClip.cpp @@ -344,6 +344,48 @@ void MidiClip::splitNotes(const NoteVector& notes, TimePos pos) } } +void MidiClip::splitNotesAlongLine(const NoteVector notes, TimePos pos1, int key1, TimePos pos2, int key2, bool deleteShortEnds) +{ + if (notes.empty()) { return; } + + // Don't split if the line is horitzontal + if (key1 == key2) { return; } + + addJournalCheckPoint(); + + const auto slope = 1.f * (pos2 - pos1) / (key2 - key1); + const auto& [minKey, maxKey] = std::minmax(key1, key2); + + for (const auto& note : notes) + { + // Skip if the key is <= to minKey, since the line is drawn from the top of minKey to the top of maxKey, but only passes through maxKey - minKey - 1 total keys. + if (note->key() <= minKey || note->key() > maxKey) { continue; } + + // Subtracting 0.5 to get the line's intercept at the "center" of the key, not the top. + const TimePos keyIntercept = slope * (note->key() - 0.5 - key1) + pos1; + if (note->pos() < keyIntercept && note->endPos() > keyIntercept) + { + auto newNote1 = Note{*note}; + newNote1.setLength(keyIntercept - note->pos()); + + auto newNote2 = Note{*note}; + newNote2.setPos(keyIntercept); + newNote2.setLength(note->endPos() - keyIntercept); + + if (deleteShortEnds) + { + addNote(newNote1.length() >= newNote2.length() ? newNote1 : newNote2, false); + } + else + { + addNote(newNote1, false); + addNote(newNote2, false); + } + + removeNote(note); + } + } +}