From 0c924c3933337be41a46df52ffdf936a3c54ac3d Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 8 Apr 2025 15:48:09 -0600 Subject: [PATCH] pianoroll: dragging start handle before zero shifts MIDI later in time --- gtk2_ardour/editor_drag.cc | 75 +++++++++++++++++++++++++++++------- gtk2_ardour/midi_view.cc | 78 ++++++++++++++++++++++++++++++++++++++ gtk2_ardour/midi_view.h | 2 + gtk2_ardour/pianoroll.cc | 9 +++++ gtk2_ardour/pianoroll.h | 2 + 5 files changed, 153 insertions(+), 13 deletions(-) diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 1dbf0c4c88..cc6c9353ca 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -7564,13 +7564,34 @@ ClipStartDrag::motion (GdkEvent* event, bool first_move) { ArdourCanvas::Rect r (original_rect); - timepos_t pos (adjusted_current_time (event)); - editing_context.snap_to_with_modifier (pos, event, Temporal::RoundNearest, ARDOUR::SnapToGrid_Scaled, true); - double pix = editing_context.timeline_to_canvas (editing_context.time_to_pixel (pos)); + double x, y; + gdk_event_get_coords (event, &x, &y); + + if (x >= editing_context.timeline_origin()) { + + /* Compute snapped position and adjust rect item if appropriate */ + + timepos_t pos = adjusted_current_time (event); + editing_context.snap_to_with_modifier (pos, event, Temporal::RoundNearest, ARDOUR::SnapToGrid_Scaled, true); + double pix = editing_context.timeline_to_canvas (editing_context.time_to_pixel (pos)); + + if (pix >= editing_context.timeline_origin()) { + r.x1 = dragging_rect->parent()->canvas_to_item (Duple (pix, 0.0)).x; + } - if (pix > editing_context.timeline_origin()) { - r.x1 = dragging_rect->parent()->canvas_to_item (Duple (pix, 0.0)).x; } else { + + /* We need to do our own math here because the normal drag + * coordinates are clamped to zero (no negative values). + */ + + x -= editing_context.timeline_origin(); + timepos_t tp (mce.pixel_to_sample (x)); + Beats b (tp.beats() * -1); + mce.shift_midi (timepos_t (b), false); + + /* ensure the line is in the right place */ + r.x1 = r.x0 + 1.; } @@ -7585,22 +7606,50 @@ ClipStartDrag::finished (GdkEvent* event, bool movement_occured) return; } - timepos_t pos = adjusted_current_time (event); + double x, y; + gdk_event_get_coords (event, &x, &y); - assert (mce.midi_view()); + if (x >= editing_context.timeline_origin()) { + + timepos_t pos = adjusted_current_time (event); + editing_context.snap_to_with_modifier (pos, event, Temporal::RoundNearest, ARDOUR::SnapToGrid_Scaled, true); + double pix = editing_context.timeline_to_canvas (editing_context.time_to_pixel (pos)); + + if (pix >= editing_context.timeline_origin()) { + + assert (mce.midi_view()); + + if (mce.midi_view()->show_source()) { + pos = mce.midi_view()->source_beats_to_timeline (pos.beats()); + } + + editing_context.snap_to_with_modifier (pos, event, Temporal::RoundNearest, ARDOUR::SnapToGrid_Scaled, true); + mce.set_trigger_start (pos); + } + + } else { + + /* We need to do our own math here because the normal drag + * coordinates are clamped to zero (no negative values). + */ + + x -= editing_context.timeline_origin(); + timepos_t tp (mce.pixel_to_sample (x)); + Beats b (tp.beats() * -1); + mce.shift_midi (timepos_t (b), true); - if (mce.midi_view()->show_source()) { - pos = mce.midi_view()->source_beats_to_timeline (pos.beats()); } - - editing_context.snap_to_with_modifier (pos, event, Temporal::RoundNearest, ARDOUR::SnapToGrid_Scaled, true); - mce.set_trigger_start (pos); } void -ClipStartDrag::aborted (bool) +ClipStartDrag::aborted (bool movement_occured) { dragging_rect->set (original_rect); + + if (movement_occured) { + /* redraw to get notes back to the right places */ + mce.shift_midi (timepos_t (Temporal::Beats()), false); + } } ClipEndDrag::ClipEndDrag (EditingContext& ec, ArdourCanvas::Rectangle& r, Pianoroll& m) diff --git a/gtk2_ardour/midi_view.cc b/gtk2_ardour/midi_view.cc index 54a22bf419..34af8a6b5d 100644 --- a/gtk2_ardour/midi_view.cc +++ b/gtk2_ardour/midi_view.cc @@ -1260,6 +1260,9 @@ MidiView::model_changed() _marked_for_selection.clear (); _marked_for_velocity.clear (); _pending_note_selection.clear (); + + size_start_rect (); + size_end_rect (); } void @@ -5270,6 +5273,81 @@ MidiView::add_split_notes () } } +void +MidiView::shift_midi (timepos_t const & t, bool model) +{ + /* INTENDED FOR USE IN PIANOROLL CONTEXT ONLY */ + + assert (_show_source); + + Beats beats (t.beats()); + + if (model) { + + /* Change the model */ + + std::string cmd = string_compose (_("Shift MIDI by %1"), beats.str()); + XMLNode& before (_midi_region->get_state()); + _editing_context.begin_reversible_command (cmd); + _model->insert_silence_at_start (beats, _editing_context.history()); + XMLNode& after (_midi_region->get_state()); + _editing_context.add_command (new MementoCommand (*(_midi_region.get()), &before, &after)); + _editing_context.commit_reversible_command (); + + } else { + + /* Only change the view */ + + for (auto & [ note, gui ] : _events) { + Temporal::Beats note_time_qn; + double dx = 0.0; + + if (_show_source) { + note_time_qn = note->time (); + } else { + note_time_qn = _midi_region->source_beats_to_absolute_beats (note->time()); + } + + if (_midi_context.note_mode() == Sustained) { + dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn + beats)); + + /*: ::item_to_canvas() converts to a global canvas + * coordinate, but ::time_to_pixel() gives us a + * timeline-relative coordinate. + * + * So we need to adjust ... + */ + + dx -= _editing_context.canvas_to_timeline (gui->item()->item_to_canvas (ArdourCanvas::Duple (gui->x0(), 0)).x); + } else { + /* Hit::x0() is offset by _position.x, unlike Note::x0() */ + Hit* hit = dynamic_cast(gui); + if (hit) { + dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn + beats)); + dx -= _editing_context.canvas_to_timeline (gui->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x); + } + } + + gui->move_event (dx, 0.0); + + /* update length, which may have changed in pixels at the new location */ + if (_midi_context.note_mode() == Sustained) { + Note* sus = dynamic_cast (gui); + double len_dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn) + t + timecnt_t (note->length())); + + /* at this point, len_dx is a timeline-relative pixel + * duration. To convert it back to an item-centric + * coordinate, we need to first convert it to a global + * canvas position. + */ + + len_dx = _editing_context.timeline_to_canvas (len_dx); + sus->set_x1 (gui->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x); + } + } + } +} + double MidiView::height() const { diff --git a/gtk2_ardour/midi_view.h b/gtk2_ardour/midi_view.h index abc402a693..701900d107 100644 --- a/gtk2_ardour/midi_view.h +++ b/gtk2_ardour/midi_view.h @@ -358,6 +358,8 @@ class MidiView : public virtual sigc::trackable, public LineMerger void note_deleted (NoteBase*); void clear_note_selection (); + void shift_midi (Temporal::timepos_t const &, bool model); + void show_verbose_cursor_for_new_note_value(std::shared_ptr current_note, uint8_t new_note) const; std::shared_ptr midi_track() const { return _midi_track; } diff --git a/gtk2_ardour/pianoroll.cc b/gtk2_ardour/pianoroll.cc index 4af56fcf32..aebf84660b 100644 --- a/gtk2_ardour/pianoroll.cc +++ b/gtk2_ardour/pianoroll.cc @@ -2822,3 +2822,12 @@ Pianoroll::allow_trim_cursors () const return mouse_mode == Editing::MouseContent || mouse_mode == Editing::MouseTimeFX; } +void +Pianoroll::shift_midi (timepos_t const & t, bool model) +{ + if (!view) { + return; + } + + view->shift_midi (t, model); +} diff --git a/gtk2_ardour/pianoroll.h b/gtk2_ardour/pianoroll.h index aa4aa198d6..b2a8f4da99 100644 --- a/gtk2_ardour/pianoroll.h +++ b/gtk2_ardour/pianoroll.h @@ -142,6 +142,8 @@ class Pianoroll : public CueEditor void set_session (ARDOUR::Session*); bool allow_trim_cursors () const; + void shift_midi (Temporal::timepos_t const &, bool model); + protected: void load_bindings (); void register_actions ();