X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=gtk2_ardour%2Fmidi_region_view.cc;h=917999405c9b52d4bf8859293f7166647c0edf6c;hb=f2495d98080db52af2bb961f03e31e858be6d4f9;hp=97cb11529e64938aac281cf94fef012417efe963;hpb=8d981025739863bac9be84241d4bff009a4cea0b;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 97cb11529e..917999405c 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -33,6 +33,7 @@ #include "pbd/stateful_diff_command.h" #include "ardour/midi_model.h" +#include "ardour/midi_playlist.h" #include "ardour/midi_region.h" #include "ardour/midi_source.h" #include "ardour/midi_track.h" @@ -103,6 +104,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _current_range_max(0) , _region_relative_time_converter(r->session().tempo_map(), r->position()) , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start()) + , _region_relative_time_converter_double(r->session().tempo_map(), r->position()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) @@ -123,6 +125,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); _note_group->raise_to_top(); @@ -149,6 +152,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _current_range_max(0) , _region_relative_time_converter(r->session().tempo_map(), r->position()) , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start()) + , _region_relative_time_converter_double(r->session().tempo_map(), r->position()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) @@ -169,6 +173,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); _note_group->raise_to_top(); @@ -200,6 +205,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _current_range_max(0) , _region_relative_time_converter(other.region_relative_time_converter()) , _source_relative_time_converter(other.source_relative_time_converter()) + , _region_relative_time_converter_double(other.region_relative_time_converter_double()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (get_canvas_group())) , _note_diff_command (0) @@ -220,6 +226,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _mouse_changed_selection (false) { init (false); } @@ -230,6 +237,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrraise_to_top(); - midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this), + midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this), boost::bind (&MidiRegionView::midi_channel_mode_changed, this), gui_context ()); @@ -386,7 +395,6 @@ MidiRegionView::canvas_group_event(GdkEvent* ev) case GDK_BUTTON_RELEASE: r = button_release (&ev->button); - _note_player.reset(); return r; case GDK_MOTION_NOTIFY: @@ -477,6 +485,11 @@ MidiRegionView::leave_internal() if (frame_handle_end) { frame_handle_end->raise_to_top(); } + + MidiTimeAxisView* mtv = dynamic_cast(&trackview); + if (mtv) { + mtv->set_note_highlight (NO_MIDI_NOTE); + } } bool @@ -502,6 +515,7 @@ MidiRegionView::button_press (GdkEventButton* ev) } _pressed_button = ev->button; + _mouse_changed_selection = false; return true; } @@ -532,12 +546,14 @@ MidiRegionView::button_release (GdkEventButton* ev) case MouseRange: /* no motion occured - simple click */ clear_selection (); + _mouse_changed_selection = true; break; case MouseContent: case MouseTimeFX: { clear_selection(); + _mouse_changed_selection = true; if (Keyboard::is_insert_note_event(ev)) { @@ -591,6 +607,11 @@ MidiRegionView::button_release (GdkEventButton* ev) break; } + if (_mouse_changed_selection) { + trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change")); + trackview.editor().commit_reversible_selection_op (); + } + return false; } @@ -643,6 +664,7 @@ MidiRegionView::motion (GdkEventMotion* ev) editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { clear_selection (); + _mouse_changed_selection = true; } _mouse_state = SelectRectDragging; return true; @@ -745,27 +767,19 @@ MidiRegionView::key_press (GdkEventKey* ev) delete_selection(); return true; - } else if (ev->keyval == GDK_Tab) { - - if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { - goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); - } else { - goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); - } - return true; - - } else if (ev->keyval == GDK_ISO_Left_Tab) { + } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) { - /* Shift-TAB generates ISO Left Tab, for some reason */ + trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note")); if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); } else { goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); } - return true; + trackview.editor().commit_reversible_selection_op(); + return true; } else if (ev->keyval == GDK_Up) { @@ -946,9 +960,12 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo view->update_note_range(new_note->note()); - MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note")); - cmd->add (new_note); - _model->apply_command(*trackview.session(), cmd); + start_note_diff_command(_("add note")); + + clear_selection (); + note_diff_add_note (new_note, true, false); + + apply_diff(); play_midi_note (new_note); } @@ -982,8 +999,8 @@ MidiRegionView::display_model(boost::shared_ptr model) content_connection.disconnect (); _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context()); - - clear_events (); + /* Don't signal as nobody else needs to know until selection has been altered. */ + clear_events (false); if (_enable_display) { redisplay_model(); @@ -994,6 +1011,7 @@ void MidiRegionView::start_note_diff_command (string name) { if (!_note_diff_command) { + trackview.editor().begin_reversible_command (name); _note_diff_command = _model->new_note_diff_command (name); } } @@ -1044,6 +1062,7 @@ void MidiRegionView::apply_diff (bool as_subcommand) { bool add_or_remove; + bool commit = false; if (!_note_diff_command) { return; @@ -1056,20 +1075,26 @@ MidiRegionView::apply_diff (bool as_subcommand) } } + midi_view()->midi_track()->midi_playlist()->region_edited( + _region, _note_diff_command); + if (as_subcommand) { _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command); } else { _model->apply_command (*trackview.session(), _note_diff_command); + commit = true; } _note_diff_command = 0; - midi_view()->midi_track()->playlist_modified(); if (add_or_remove) { _marked_for_selection.clear(); } _marked_for_velocity.clear(); + if (commit) { + trackview.editor().commit_reversible_command (); + } } void @@ -1100,6 +1125,21 @@ MidiRegionView::find_canvas_note (boost::shared_ptr note) return 0; } +/** This version finds any canvas note matching the supplied note. */ +NoteBase* +MidiRegionView::find_canvas_note (NoteType note) +{ + Events::iterator it; + + for (it = _events.begin(); it != _events.end(); ++it) { + if (*((*it)->note()) == note) { + return *it; + } + } + + return 0; +} + void MidiRegionView::get_events (Events& e, Evoral::Sequence::NoteOperator op, uint8_t val, int chan_mask) { @@ -1170,7 +1210,14 @@ MidiRegionView::redisplay_model() } else { - add_note (note, visible); + cne = add_note (note, visible); + } + + set >::iterator it; + for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) { + if (*(*it) == *note) { + add_to_selection (cne); + } } } else { @@ -1182,7 +1229,6 @@ MidiRegionView::redisplay_model() } } - /* remove note items that are no longer valid */ if (!empty_when_starting) { @@ -1213,6 +1259,7 @@ MidiRegionView::redisplay_model() _marked_for_selection.clear (); _marked_for_velocity.clear (); + _pending_note_selection.clear (); /* we may have caused _events to contain things out of order (e.g. if a note moved earlier or later). we don't generally need them in time order, but @@ -1370,6 +1417,7 @@ MidiRegionView::region_resized (const PropertyChange& what_changed) if (what_changed.contains (ARDOUR::Properties::position)) { _region_relative_time_converter.set_origin_b(_region->position()); + _region_relative_time_converter_double.set_origin_b(_region->position()); set_duration(_region->length(), 0); if (_enable_display) { redisplay_model(); @@ -1460,6 +1508,21 @@ MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force) const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note())); const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.); + if (y0 < 0 || y1 >= _height) { + /* During DnD, the region uses the 'old/current' + * midi_stream_view()'s range and its position/height calculation. + * + * Ideally DnD would decouple the midi_stream_view() for the + * region(s) being dragged and set it to the target's range + * (or in case of the drop-zone, FullRange). + * but I don't see how this can be done without major rework. + * + * For now, just prevent visual bleeding of events in case + * the target-track is smaller. + */ + event->hide(); + continue; + } cnote->set_y0 (y0); cnote->set_y1 (y1); @@ -1608,21 +1671,25 @@ MidiRegionView::start_playing_midi_chord (vector > n return; } - _note_player = boost::shared_ptr(new NotePlayer(route_ui->midi_track())); + NotePlayer* player = new NotePlayer (route_ui->midi_track()); for (vector >::iterator n = notes.begin(); n != notes.end(); ++n) { - _note_player->add (*n); + player->add (*n); } - _note_player->on (); + player->play (); } bool MidiRegionView::note_in_region_range (const boost::shared_ptr note, bool& visible) const { + /* This is imprecise due to all the conversion conversion involved, so only + hide notes if they seem to start more than one tick before the start. */ + const framecnt_t tick_frames = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate()); const framepos_t note_start_frames = source_beats_to_region_frames (note->time()); - bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame()); + const bool outside = ((note_start_frames <= -tick_frames) || + (note_start_frames >= _region->length())); visible = (note->note() >= midi_stream_view()->lowest_note()) && (note->note() <= midi_stream_view()->highest_note()); @@ -1712,6 +1779,13 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions) const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.); const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5; + // see DnD note in MidiRegionView::apply_note_range() above + if (y <= 0 || y >= _height) { + ev->hide(); + } else { + ev->show(); + } + ev->set_position (ArdourCanvas::Duple (x, y)); ev->set_height (diamond_size); @@ -1735,7 +1809,7 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions) * notes, and resolve_note should be called when the corresponding note off * event arrives, to properly display the note. */ -void +NoteBase* MidiRegionView::add_note(const boost::shared_ptr note, bool visible) { NoteBase* event = 0; @@ -1793,6 +1867,7 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) MidiStreamView* const view = mtv->midi_view(); view->update_note_range (note->note()); + return event; } void @@ -1816,10 +1891,12 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity view->update_note_range(new_note->note()); _marked_for_selection.clear (); - clear_selection (); start_note_diff_command (_("step add")); + + clear_selection (); note_diff_add_note (new_note, true, false); + apply_diff(); // last_step_edit_note = new_note; @@ -1909,7 +1986,9 @@ MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Nam void MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch) { - MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); + string name = _("alter patch change"); + trackview.editor().begin_reversible_command (name); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); if (pc.patch()->program() != new_patch.program()) { c->change_program (pc.patch (), new_patch.program()); @@ -1921,6 +2000,7 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri } _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -1929,7 +2009,9 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri void MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange & new_change) { - MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); + string name = _("alter patch change"); + trackview.editor().begin_reversible_command (name); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); if (old_change->time() != new_change.time()) { c->change_time (old_change, new_change.time()); @@ -1948,6 +2030,7 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const } _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -1962,8 +2045,10 @@ void MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange const & patch) { MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + string name = _("add patch change"); - MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change")); + trackview.editor().begin_reversible_command (name); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); c->add (MidiModel::PatchChangePtr ( new Evoral::PatchChange ( absolute_frames_to_source_beats (_region->position() + t), @@ -1973,6 +2058,7 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChangeapply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -1981,9 +2067,11 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChangenew_patch_change_diff_command (_("move patch change")); c->change_time (pc.patch (), t); _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -1992,9 +2080,11 @@ MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t) void MidiRegionView::delete_patch_change (PatchChange* pc) { + trackview.editor().begin_reversible_command (_("delete patch change")); MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change")); c->remove (pc->patch ()); _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -2143,6 +2233,24 @@ MidiRegionView::invert_selection () } } +/** Used for selection undo/redo. + The requested notes most likely won't exist in the view until the next model redisplay. +*/ +void +MidiRegionView::select_notes (list > notes) +{ + NoteBase* cne; + list >::iterator n; + + for (n = notes.begin(); n != notes.end(); ++n) { + if ((cne = find_canvas_note(*(*n))) != 0) { + add_to_selection (cne); + } else { + _pending_note_selection.insert(*n); + } + } +} + void MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend) { @@ -2521,22 +2629,25 @@ MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) } /** @param x Pixel relative to the region position. + * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap. + * Used for inverting the snap logic with key modifiers and snap delta calculation. * @return Snapped frame relative to the region position. */ framepos_t -MidiRegionView::snap_pixel_to_sample(double x) +MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap) { PublicEditor& editor (trackview.editor()); - return snap_frame_to_frame (editor.pixel_to_sample (x)); + return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap); } /** @param x Pixel relative to the region position. + * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation). * @return Snapped pixel relative to the region position. */ double -MidiRegionView::snap_to_pixel(double x) +MidiRegionView::snap_to_pixel(double x, bool ensure_snap) { - return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x)); + return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap)); } double @@ -2587,6 +2698,12 @@ MidiRegionView::region_frames_to_region_beats(framepos_t frames) const return _region_relative_time_converter.from(frames); } +double +MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const +{ + return _region_relative_time_converter_double.from(frames); +} + void MidiRegionView::begin_resizing (bool /*at_front*/) { @@ -2635,9 +2752,11 @@ MidiRegionView::begin_resizing (bool /*at_front*/) * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point * as the \a primary note. + * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode. + * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used. */ void -MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative) +MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { bool cursor_set = false; @@ -2648,15 +2767,15 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ if (at_front) { if (relative) { - current_x = canvas_note->x0() + delta_x; + current_x = canvas_note->x0() + delta_x + snap_delta; } else { - current_x = primary->x0() + delta_x; + current_x = primary->x0() + delta_x + snap_delta; } } else { if (relative) { - current_x = canvas_note->x1() + delta_x; + current_x = canvas_note->x1() + delta_x + snap_delta; } else { - current_x = primary->x1() + delta_x; + current_x = primary->x1() + delta_x + snap_delta; } } @@ -2670,26 +2789,47 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ } if (at_front) { - resize_rect->set_x0 (snap_to_pixel(current_x)); + if (with_snap) { + resize_rect->set_x0 (snap_to_pixel(current_x) - snap_delta); + } else { + resize_rect->set_x0 (current_x - snap_delta); + } resize_rect->set_x1 (canvas_note->x1()); } else { - resize_rect->set_x1 (snap_to_pixel(current_x)); + if (with_snap) { + resize_rect->set_x1 (snap_to_pixel(current_x) - snap_delta); + } else { + resize_rect->set_x1 (current_x - snap_delta); + } resize_rect->set_x0 (canvas_note->x0()); } if (!cursor_set) { - const double snapped_x = snap_pixel_to_sample (current_x); + /* Convert snap delta from pixels to beats. */ + framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta); + double snap_delta_beats = 0.0; + int sign = 1; + + /* negative beat offsets aren't allowed */ + if (snap_delta_samps > 0) { + snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps); + } else if (snap_delta_samps < 0) { + snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps); + sign = -1; + } + + const double snapped_x = (with_snap ? snap_pixel_to_sample (current_x) : trackview.editor ().pixel_to_sample (current_x)); Evoral::Beats beats = region_frames_to_region_beats (snapped_x); Evoral::Beats len = Evoral::Beats(); if (at_front) { if (beats < canvas_note->note()->end_time()) { - len = canvas_note->note()->time() - beats; + len = canvas_note->note()->time() - beats + (sign * snap_delta_beats); len += canvas_note->note()->length(); } } else { if (beats >= canvas_note->note()->time()) { - len = beats - canvas_note->note()->time(); + len = beats - canvas_note->note()->time() - (sign * snap_delta_beats); } } @@ -2710,9 +2850,9 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ * Parameters the same as for \a update_resizing(). */ void -MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative) +MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { - start_note_diff_command (_("resize notes")); + _note_diff_command = _model->new_note_diff_command (_("resize notes")); for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { Note* canvas_note = (*i)->note; @@ -2726,15 +2866,15 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ if (at_front) { if (relative) { - current_x = canvas_note->x0() + delta_x; + current_x = canvas_note->x0() + delta_x + snap_delta; } else { - current_x = primary->x0() + delta_x; + current_x = primary->x0() + delta_x + snap_delta; } } else { if (relative) { - current_x = canvas_note->x1() + delta_x; + current_x = canvas_note->x1() + delta_x + snap_delta; } else { - current_x = primary->x1() + delta_x; + current_x = primary->x1() + delta_x + snap_delta; } } @@ -2745,16 +2885,32 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ current_x = trackview.editor().sample_to_pixel(_region->length()); } - /* Convert that to a frame within the source */ - current_x = snap_pixel_to_sample (current_x) + _region->start (); + /* Convert snap delta from pixels to beats with sign. */ + framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta); + double snap_delta_beats = 0.0; + int sign = 1; + + if (snap_delta_samps > 0) { + snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps); + } else if (snap_delta_samps < 0) { + snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps); + sign = -1; + } + + /* Convert the new x position to a frame within the source */ + framepos_t current_fr; + if (with_snap) { + current_fr = snap_pixel_to_sample (current_x) + _region->start (); + } else { + current_fr = trackview.editor().pixel_to_sample (current_x) + _region->start (); + } /* and then to beats */ - const Evoral::Beats x_beats = region_frames_to_region_beats (current_x); + const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr); if (at_front && x_beats < canvas_note->note()->end_time()) { - note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats); - - Evoral::Beats len = canvas_note->note()->time() - x_beats; + note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats)); + Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats); len += canvas_note->note()->length(); if (!!len) { @@ -2763,8 +2919,8 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ } if (!at_front) { - const Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0), - x_beats - canvas_note->note()->time()); + Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0), + x_beats - canvas_note->note()->time() - (sign * snap_delta_beats)); note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); } @@ -2773,7 +2929,7 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ } _resize_data.clear(); - apply_diff(); + apply_diff(true); } void @@ -3351,10 +3507,12 @@ MidiRegionView::selection_as_cut_buffer () const bool MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx) { + bool commit = false; // Paste notes, if available MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes()); if (m != selection.midi_notes.end()) { ctx.counts.increase_n_notes(); + if (!(*m)->empty()) { commit = true; } paste_internal(pos, ctx.count, ctx.times, **m); } @@ -3362,9 +3520,14 @@ MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContex typedef RouteTimeAxisView::AutomationTracks ATracks; const ATracks& atracks = midi_view()->automation_tracks(); for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) { - a->second->paste(pos, selection, ctx); + if (a->second->paste(pos, selection, ctx)) { + commit = true; + } } + if (commit) { + trackview.editor().commit_reversible_command (); + } return true; } @@ -3848,7 +4011,7 @@ MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const std::string name; - MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + MidiTimeAxisView* mtv = dynamic_cast(&trackview); if (mtv) { boost::shared_ptr device_names(mtv->get_device_names()); if (device_names) { @@ -3860,6 +4023,7 @@ MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const patch_key.program(), n->note()); } + mtv->set_note_highlight (n->note()); } char buf[128]; @@ -3944,12 +4108,6 @@ MidiRegionView::selection_cleared (MidiRegionView* rv) clear_selection (false); } -void -MidiRegionView::note_button_release () -{ - _note_player.reset(); -} - ChannelMode MidiRegionView::get_channel_mode () const {