X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=c0d8a3c59968892b545cbc6b98a8fda0198cc5ec;hb=47d186d1a301c8cbc8ed8c3b6cbe0c84bb6b681f;hp=682f02365c89400dceeab8d1a44616d8f86e7e10;hpb=e7b8e98db26bc2b9ee2587f388de2dbbf7ca5837;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 682f02365c..c0d8a3c599 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -82,7 +82,7 @@ #include "sys_ex.h" #include "ui_config.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace ARDOUR; using namespace PBD; @@ -90,8 +90,6 @@ using namespace Editing; using namespace std; using Gtkmm2ext::Keyboard; -PBD::Signal1 MidiRegionView::SelectionCleared; - #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1) MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, @@ -113,7 +111,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) , _channel_selection_scoped_note (0) - , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) , _sort_needed (true) @@ -125,6 +122,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _entered_note (0) , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); @@ -133,11 +131,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); connect_to_diskstream (); - - SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); - - PublicEditor& editor (trackview.editor()); - editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ()); } MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, @@ -161,7 +154,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) , _channel_selection_scoped_note (0) - , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) , _sort_needed (true) @@ -173,6 +165,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _entered_note (0) , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); @@ -181,11 +174,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); connect_to_diskstream (); - - SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); - - PublicEditor& editor (trackview.editor()); - editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ()); } void @@ -214,7 +202,6 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) , _channel_selection_scoped_note (0) - , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) , _sort_needed (true) @@ -226,6 +213,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _entered_note (0) , _mouse_changed_selection (false) { init (false); @@ -246,7 +234,6 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); connect_to_diskstream (); - - SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); - - PublicEditor& editor (trackview.editor()); - editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ()); } InstrumentInfo& @@ -408,7 +391,7 @@ MidiRegionView::canvas_group_event(GdkEvent* ev) bool MidiRegionView::enter_notify (GdkEventCrossing* ev) { - enter_internal(); + enter_internal (ev->state); _entered = true; return false; @@ -430,22 +413,35 @@ MidiRegionView::mouse_mode_changed () set_frame_color(); if (_entered) { - if (trackview.editor().internal_editing()) { - // Switched in to internal editing mode while entered - enter_internal(); - } else { - // Switched out of internal editing mode while entered + if (!trackview.editor().internal_editing()) { + /* Switched out of internal editing mode while entered. + Only necessary for leave as a mouse_mode_change over a region + automatically triggers an enter event. */ leave_internal(); } + else if (trackview.editor().current_mouse_mode() == MouseContent) { + // hide cursor and ghost note after changing to internal edit mode + remove_ghost_note (); + + /* XXX This is problematic as the function is executed for every region + and only for one region _entered_note can be true. Still it's + necessary as to hide the verbose cursor when we're changing from + draw mode to internal edit mode. These lines are the reason why + in some situations no verbose cursor is shown when we enter internal + edit mode over a note. */ + if (!_entered_note) { + hide_verbose_cursor (); + } + } } } void -MidiRegionView::enter_internal() +MidiRegionView::enter_internal (uint32_t state) { if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) { // Show ghost note under pencil - create_ghost_note(_last_event_x, _last_event_y); + create_ghost_note(_last_event_x, _last_event_y, state); } if (!_selection.empty()) { @@ -468,6 +464,7 @@ MidiRegionView::leave_internal() { hide_verbose_cursor (); remove_ghost_note (); + _entered_note = 0; if (_grabbed_keyboard) { Keyboard::magic_widget_drop_focus(); @@ -500,7 +497,21 @@ MidiRegionView::button_press (GdkEventButton* ev) if (_mouse_state != SelectTouchDragging) { _pressed_button = ev->button; - _mouse_state = Pressed; + + if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) { + + if (midi_view()->note_mode() == Percussive) { + editor->drags()->set (new HitCreateDrag (dynamic_cast (editor), group, this), (GdkEvent *) ev); + } else { + editor->drags()->set (new NoteCreateDrag (dynamic_cast (editor), group, this), (GdkEvent *) ev); + } + + _mouse_state = AddDragging; + remove_ghost_note (); + hide_verbose_cursor (); + } else { + _mouse_state = Pressed; + } return true; } @@ -535,50 +546,22 @@ MidiRegionView::button_release (GdkEventButton* ev) switch (editor.current_mouse_mode()) { case MouseRange: - /* no motion occured - simple click */ - clear_selection (); + /* no motion occurred - simple click */ + clear_editor_note_selection (); _mouse_changed_selection = true; break; case MouseContent: case MouseTimeFX: { - clear_selection(); _mouse_changed_selection = true; - - if (Keyboard::is_insert_note_event(ev)) { - - double event_x, event_y; - - event_x = ev->x; - event_y = ev->y; - group->canvas_to_item (event_x, event_y); - - Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x)); - - /* Shorten the length by 1 tick so that we can add a new note at the next - grid snap without it overlapping this one. - */ - beats -= Evoral::Beats::tick(); - - create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); - } + clear_editor_note_selection (); break; } case MouseDraw: - { - Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x)); - - /* Shorten the length by 1 tick so that we can add a new note at the next - grid snap without it overlapping this one. - */ - beats -= Evoral::Beats::tick(); - - create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); + break; - break; - } default: break; } @@ -586,11 +569,12 @@ MidiRegionView::button_release (GdkEventButton* ev) _mouse_state = None; break; - case SelectRectDragging: case AddDragging: + /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion. + we don't want one when we were drag-selecting either. */ + case SelectRectDragging: editor.drags()->end_grab ((GdkEvent *) ev); _mouse_state = None; - create_ghost_note (ev->x, ev->y); break; @@ -611,25 +595,38 @@ MidiRegionView::motion (GdkEventMotion* ev) { PublicEditor& editor = trackview.editor (); - if (!_ghost_note && editor.current_mouse_mode() == MouseContent && - Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) && - _mouse_state != AddDragging) { + if (!_entered_note) { + + if (_mouse_state == AddDragging) { + if (_ghost_note) { + remove_ghost_note (); + } + + } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) && + _mouse_state != AddDragging) { - create_ghost_note (ev->x, ev->y); + create_ghost_note (ev->x, ev->y, ev->state); - } else if (_ghost_note && editor.current_mouse_mode() == MouseContent && - Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { + } else if (_ghost_note && editor.current_mouse_mode() == MouseContent && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { - update_ghost_note (ev->x, ev->y); + update_ghost_note (ev->x, ev->y, ev->state); - } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) { + } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) { - remove_ghost_note (); - hide_verbose_cursor (); + remove_ghost_note (); + hide_verbose_cursor (); - } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) { + } else if (editor.current_mouse_mode() == MouseDraw) { - update_ghost_note (ev->x, ev->y); + if (_ghost_note) { + update_ghost_note (ev->x, ev->y, ev->state); + } + else { + create_ghost_note (ev->x, ev->y, ev->state); + } + } } /* any motion immediately hides velocity text that may have been visible */ @@ -645,16 +642,10 @@ MidiRegionView::motion (GdkEventMotion* ev) MouseMode m = editor.current_mouse_mode(); - if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) { - editor.drags()->set (new NoteCreateDrag (dynamic_cast (&editor), group, this), (GdkEvent *) ev); - _mouse_state = AddDragging; - remove_ghost_note (); - hide_verbose_cursor (); - return true; - } else if (m == MouseContent) { + if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { - clear_selection (); + clear_editor_note_selection (); _mouse_changed_selection = true; } _mouse_state = SelectRectDragging; @@ -696,9 +687,10 @@ MidiRegionView::scroll (GdkEventScroll* ev) return false; } - if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { - /* XXX: bit of a hack; allow PrimaryModifier scroll through so that - it still works for zoom. + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) || + Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll + * through so that it still works for navigation. */ return false; } @@ -706,7 +698,8 @@ MidiRegionView::scroll (GdkEventScroll* ev) hide_verbose_cursor (); bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); - bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); + Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier); + bool together = Keyboard::modifier_state_contains (ev->state, mask_together); if (ev->direction == GDK_SCROLL_UP) { change_velocities (true, fine, false, together); @@ -727,15 +720,18 @@ MidiRegionView::key_press (GdkEventKey* ev) detectable auto-repeat is the name of the game and only sends repeated presses, carry out key actions at key press, not release. */ - bool unmodified = Keyboard::no_modifier_keys_pressed (ev); if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) { - _mouse_state = SelectTouchDragging; + + if (_mouse_state != AddDragging) { + _mouse_state = SelectTouchDragging; + } + return true; } else if (ev->keyval == GDK_Escape && unmodified) { - clear_selection(); + clear_editor_note_selection (); _mouse_state = None; } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) { @@ -920,7 +916,7 @@ MidiRegionView::show_list_editor () * \param snap_t true to snap t to the grid, otherwise false. */ void -MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bool snap_t) +MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap) { if (length < 2 * DBL_EPSILON) { return; @@ -928,15 +924,15 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo MidiTimeAxisView* const mtv = dynamic_cast(&trackview); MidiStreamView* const view = mtv->midi_view(); + boost::shared_ptr mr = boost::dynamic_pointer_cast (_region); - // Start of note in frames relative to region start - if (snap_t) { - framecnt_t grid_frames; - t = snap_frame_to_grid_underneath (t, grid_frames); + if (!mr) { + return; } - const MidiModel::TimeType beat_time = region_frames_to_region_beats( - t + _region->start()); + // Start of note in frames relative to region start + const int32_t divisions = trackview.editor().get_grid_music_divisions (state); + Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap); const double note = view->y_to_note(y); const uint8_t chan = mtv->get_channel_for_add(); @@ -953,7 +949,7 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo start_note_diff_command(_("add note")); - clear_selection (); + clear_editor_note_selection (); note_diff_add_note (new_note, true, false); apply_diff(); @@ -962,9 +958,10 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo } void -MidiRegionView::clear_events (bool with_selection_signal) +MidiRegionView::clear_events () { - clear_selection (with_selection_signal); + // clear selection without signaling + clear_selection_internal (); MidiGhostRegion* gr; for (std::vector::iterator g = ghosts.begin(); g != ghosts.end(); ++g) { @@ -991,7 +988,7 @@ 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()); /* Don't signal as nobody else needs to know until selection has been altered. */ - clear_events (false); + clear_events (); if (_enable_display) { redisplay_model(); @@ -1093,7 +1090,7 @@ MidiRegionView::abort_command() { delete _note_diff_command; _note_diff_command = 0; - clear_selection(); + clear_editor_note_selection(); } NoteBase* @@ -1118,12 +1115,12 @@ MidiRegionView::find_canvas_note (boost::shared_ptr note) /** This version finds any canvas note matching the supplied note. */ NoteBase* -MidiRegionView::find_canvas_note (NoteType note) +MidiRegionView::find_canvas_note (Evoral::event_id_t id) { Events::iterator it; for (it = _events.begin(); it != _events.end(); ++it) { - if (*((*it)->note()) == note) { + if ((*it)->note()->id() == id) { return *it; } } @@ -1179,21 +1176,20 @@ MidiRegionView::redisplay_model() _optimization_iterator = _events.begin(); bool empty_when_starting = _events.empty(); + NoteBase* cne; for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { boost::shared_ptr note (*n); - NoteBase* cne; bool visible; if (note_in_region_range (note, visible)) { if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { - cne->validate (); - update_note (cne); - if (visible) { + cne->validate (); + update_note (cne); cne->show (); } else { cne->hide (); @@ -1204,9 +1200,9 @@ MidiRegionView::redisplay_model() cne = add_note (note, visible); } - set >::iterator it; + set::iterator it; for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) { - if (*(*it) == *note) { + if ((*it) == note->id()) { add_to_selection (cne); } } @@ -1387,36 +1383,39 @@ MidiRegionView::~MidiRegionView () if (_active_notes) { end_write(); } - - _selection_cleared_connection.disconnect (); - + _entered_note = 0; _selection.clear(); - clear_events (false); + clear_events (); delete _note_group; delete _note_diff_command; delete _step_edit_cursor; - delete _temporary_note_group; } void MidiRegionView::region_resized (const PropertyChange& what_changed) { - RegionView::region_resized(what_changed); + RegionView::region_resized(what_changed); // calls RegionView::set_duration() 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(); - } + /* reset_width dependent_items() redisplays model */ + } if (what_changed.contains (ARDOUR::Properties::start) || what_changed.contains (ARDOUR::Properties::position)) { _source_relative_time_converter.set_origin_b (_region->position() - _region->start()); } + /* catch end and start trim so we can update the view*/ + if (!what_changed.contains (ARDOUR::Properties::start) && + what_changed.contains (ARDOUR::Properties::length)) { + enable_display (true); + } else if (what_changed.contains (ARDOUR::Properties::start) && + what_changed.contains (ARDOUR::Properties::length)) { + enable_display (true); + } } void @@ -1532,21 +1531,20 @@ MidiRegionView::add_ghost (TimeAxisView& tv) /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group to allow having midi notes on top of note lines and waveforms. */ - ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position); + ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position); } else { - ghost = new MidiGhostRegion (tv, trackview, unit_position); + ghost = new MidiGhostRegion (*this, tv, trackview, unit_position); } for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { ghost->add_note(*i); } + ghost->set_colors (); ghost->set_height (); ghost->set_duration (_region->length() / samples_per_pixel); ghosts.push_back (ghost); - GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context()); - return ghost; } @@ -1673,12 +1671,11 @@ MidiRegionView::start_playing_midi_chord (vector > n 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()); - const bool outside = ((note_start_frames <= -tick_frames) || - (note_start_frames >= _region->length())); + const boost::shared_ptr midi_reg = midi_region(); + + /* must compare double explicitly as Beats::operator< rounds to ppqn */ + const bool outside = (note->time().to_double() < midi_reg->start_beats() || + note->time().to_double() > midi_reg->start_beats() + midi_reg->length_beats()); visible = (note->note() >= midi_stream_view()->lowest_note()) && (note->note() <= midi_stream_view()->highest_note()); @@ -1705,23 +1702,36 @@ MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions) void MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions) { + TempoMap& map (trackview.session()->tempo_map()); + const boost::shared_ptr mr = midi_region(); boost::shared_ptr note = ev->note(); - const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time())); - const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note())); - ev->set_x0 (x); - ev->set_y0 (y0); + const double session_source_start = _region->quarter_note() - mr->start_beats(); + const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position(); + + const double x0 = trackview.editor().sample_to_pixel (note_start_frames); + double x1; + const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note())); + double y1; /* trim note display to not overlap the end of its region */ + if (note->length().to_double() > 0.0) { + double note_end_time = note->end_time().to_double(); - if (note->length() > 0) { - const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length()); - ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames)); + if (note_end_time > mr->start_beats() + mr->length_beats()) { + note_end_time = mr->start_beats() + mr->length_beats(); + } + + const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position(); + + x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1; } else { - ev->set_x1 (trackview.editor().sample_to_pixel (_region->length())); + x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1; } - ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1)); + y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1); + + ev->set (ArdourCanvas::Rect (x0, y0, x1, y1)); if (!note->length()) { if (_active_notes && note->note() < 128) { @@ -1729,7 +1739,7 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions) if (old_rect) { /* There is an active note on this key, so we have a stuck note. Finish the old rectangle here. */ - old_rect->set_x1 (x); + old_rect->set_x1 (x1); old_rect->set_outline_all (); } _active_notes[note->note()] = ev; @@ -1745,8 +1755,9 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions) } // Update color in case velocity has changed - ev->set_fill_color(ev->base_color()); - ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected())); + const uint32_t base_col = ev->base_color(); + ev->set_fill_color(base_col); + ev->set_outline_color(ev->calculate_outline(base_col, ev->selected())); if (update_ghost_regions) { for (std::vector::iterator i = ghosts.begin(); i != ghosts.end(); ++i) { @@ -1763,7 +1774,9 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions) { boost::shared_ptr note = ev->note(); - const framepos_t note_start_frames = source_beats_to_region_frames(note->time()); + const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats()); + const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position(); + const double x = trackview.editor().sample_to_pixel(note_start_frames); 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; @@ -1779,14 +1792,15 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions) ev->set_height (diamond_size); // Update color in case velocity has changed - ev->set_fill_color(ev->base_color()); - ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected())); + const uint32_t base_col = ev->base_color(); + ev->set_fill_color(base_col); + ev->set_outline_color(ev->calculate_outline(base_col, ev->selected())); if (update_ghost_regions) { for (std::vector::iterator i = ghosts.begin(); i != ghosts.end(); ++i) { MidiGhostRegion* gr = dynamic_cast (*i); if (gr) { - gr->update_note (ev); + gr->update_hit (ev); } } } @@ -1871,7 +1885,8 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity framepos_t region_end = _region->last_frame(); if (end_frame > region_end) { - _region->set_length (end_frame - _region->position()); + /* XX sets length in beats from audio space. make musical */ + _region->set_length (end_frame - _region->position(), 0); } MidiTimeAxisView* const mtv = dynamic_cast(&trackview); @@ -1883,7 +1898,7 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity start_note_diff_command (_("step add")); - clear_selection (); + clear_editor_note_selection (); note_diff_add_note (new_note, true, false); apply_diff(); @@ -2094,6 +2109,10 @@ MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta) void MidiRegionView::note_deleted (NoteBase* cne) { + if (_entered_note && cne == _entered_note) { + _entered_note = 0; + } + if (_selection.empty()) { return; } @@ -2108,6 +2127,10 @@ MidiRegionView::delete_selection() return; } + if (trackview.editor().drags()->active()) { + return; + } + start_note_diff_command (_("delete selection")); for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { @@ -2119,6 +2142,7 @@ MidiRegionView::delete_selection() _selection.clear(); apply_diff (); + hide_verbose_cursor (); } @@ -2133,65 +2157,50 @@ MidiRegionView::delete_note (boost::shared_ptr n) } void -MidiRegionView::clear_selection_except (NoteBase* ev, bool signal) +MidiRegionView::clear_editor_note_selection () +{ + DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n"); + PublicEditor& editor(trackview.editor()); + editor.get_selection().clear_midi_notes(); +} + +void +MidiRegionView::clear_selection () { - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { - if ((*i) != ev) { - Selection::iterator tmp = i; - ++tmp; + clear_selection_internal(); + PublicEditor& editor(trackview.editor()); + editor.get_selection().remove(this); +} - (*i)->set_selected (false); - (*i)->hide_velocity (); - _selection.erase (i); +void +MidiRegionView::clear_selection_internal () +{ + DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n"); - i = tmp; - } else { - ++i; - } + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + (*i)->set_selected(false); + (*i)->hide_velocity(); } + _selection.clear(); - if (!ev && _entered) { + if (_entered) { // Clearing selection entirely, ungrab keyboard Keyboard::magic_widget_drop_focus(); _grabbed_keyboard = false; } - - /* this does not change the status of this regionview w.r.t the editor - selection. - */ - - if (signal) { - SelectionCleared (this); /* EMIT SIGNAL */ - } } void MidiRegionView::unique_select(NoteBase* ev) { - const bool selection_was_empty = _selection.empty(); - - clear_selection_except (ev); - - /* don't bother with checking to see if we should remove this - regionview from the editor selection, since we're about to add - another note, and thus put/keep this regionview in the editor - selection anyway. - */ - - if (!ev->selected()) { - add_to_selection (ev); - if (selection_was_empty && _entered) { - // Grab keyboard for moving notes with arrow keys - Keyboard::magic_widget_grab_focus(); - _grabbed_keyboard = true; - } - } + clear_editor_note_selection(); + add_to_selection(ev); } void MidiRegionView::select_all_notes () { - clear_selection (); + clear_editor_note_selection (); for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { add_to_selection (*i); @@ -2201,7 +2210,7 @@ MidiRegionView::select_all_notes () void MidiRegionView::select_range (framepos_t start, framepos_t end) { - clear_selection (); + clear_editor_note_selection (); for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { framepos_t t = source_beats_to_absolute_frames((*i)->note()->time()); @@ -2227,13 +2236,13 @@ MidiRegionView::invert_selection () The requested notes most likely won't exist in the view until the next model redisplay. */ void -MidiRegionView::select_notes (list > notes) +MidiRegionView::select_notes (list notes) { NoteBase* cne; - list >::iterator n; + list::iterator n; for (n = notes.begin(); n != notes.end(); ++n) { - if ((cne = find_canvas_note(*(*n))) != 0) { + if ((cne = find_canvas_note(*n)) != 0) { add_to_selection (cne); } else { _pending_note_selection.insert(*n); @@ -2266,7 +2275,7 @@ MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, b } if (!add) { - clear_selection (); + clear_editor_note_selection (); if (!extend && (low_note == high_note) && (high_note == notenum)) { /* only note previously selected is the one we are @@ -2341,11 +2350,8 @@ void MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend) { if (!add) { - clear_selection_except (ev); - if (!_selection.empty()) { - PublicEditor& editor (trackview.editor()); - editor.get_selection().add (this); - } + clear_editor_note_selection(); + add_to_selection (ev); } if (!extend) { @@ -2583,14 +2589,15 @@ MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) if (highest_note_in_selection + dnote > 127) { highest_note_difference = highest_note_in_selection - 127; } + TempoMap& map (trackview.session()->tempo_map()); start_note_diff_command (_("move notes")); for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { - framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt; - Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames); - + double const start_qn = _region->quarter_note() - midi_region()->start_beats(); + framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt; + Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn); if (new_time < 0) { continue; } @@ -2748,7 +2755,9 @@ MidiRegionView::begin_resizing (bool /*at_front*/) void MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { + TempoMap& tmap (trackview.session()->tempo_map()); bool cursor_set = false; + bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic; for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect; @@ -2780,14 +2789,14 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ if (at_front) { if (with_snap) { - resize_rect->set_x0 (snap_to_pixel(current_x) - snap_delta); + resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta); } else { resize_rect->set_x0 (current_x - snap_delta); } resize_rect->set_x1 (canvas_note->x1()); } else { if (with_snap) { - resize_rect->set_x1 (snap_to_pixel(current_x) - snap_delta); + resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta); } else { resize_rect->set_x1 (current_x - snap_delta); } @@ -2808,9 +2817,19 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ 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(); + double snapped_x; + int32_t divisions = 0; + + if (with_snap) { + snapped_x = snap_pixel_to_sample (current_x, ensure_snap); + divisions = trackview.editor().get_grid_music_divisions (0); + } else { + snapped_x = trackview.editor ().pixel_to_sample (current_x); + } + const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions) + - midi_region()->beat()) + midi_region()->start_beats(); + + Evoral::Beats len = Evoral::Beats(); if (at_front) { if (beats < canvas_note->note()->end_time()) { @@ -2843,6 +2862,10 @@ void MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { _note_diff_command = _model->new_note_diff_command (_("resize notes")); + TempoMap& tmap (trackview.session()->tempo_map()); + + /* XX why doesn't snap_pixel_to_sample() handle this properly? */ + bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic; for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { Note* canvas_note = (*i)->note; @@ -2887,16 +2910,20 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ sign = -1; } + uint32_t divisions = 0; /* 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 (); + current_fr = snap_pixel_to_sample (current_x, ensure_snap); + divisions = trackview.editor().get_grid_music_divisions (0); } else { - current_fr = trackview.editor().pixel_to_sample (current_x) + _region->start (); + current_fr = trackview.editor().pixel_to_sample (current_x); } /* and then to beats */ - const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr); + const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions); + const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats(); + const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start); if (at_front && x_beats < canvas_note->note()->end_time()) { note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats)); @@ -3245,7 +3272,7 @@ MidiRegionView::nudge_notes (bool forward, bool fine) if (!fine) { /* non-fine, move by 1 bar regardless of snap */ - delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar()); + delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar()); } else if (trackview.editor().snap_mode() == Editing::SnapOff) { @@ -3312,13 +3339,22 @@ MidiRegionView::change_channel(uint8_t channel) void MidiRegionView::note_entered(NoteBase* ev) { + _entered_note = ev; + Editor* editor = dynamic_cast(&trackview.editor()); if (_mouse_state == SelectTouchDragging) { + note_selected (ev, true); + } else if (editor->current_mouse_mode() == MouseContent) { + + remove_ghost_note (); show_verbose_cursor (ev->note ()); + } else if (editor->current_mouse_mode() == MouseDraw) { + + remove_ghost_note (); show_verbose_cursor (ev->note ()); } } @@ -3326,6 +3362,8 @@ MidiRegionView::note_entered(NoteBase* ev) void MidiRegionView::note_left (NoteBase*) { + _entered_note = 0; + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { (*i)->hide_velocity (); } @@ -3337,9 +3375,8 @@ void MidiRegionView::patch_entered (PatchChange* p) { ostringstream s; - /* XXX should get patch name if we can */ s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n' - << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n' + << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n' << _("Channel ") << ((int) p->patch()->channel() + 1); show_verbose_cursor (s.str(), 10, 20); p->item().grab_focus(); @@ -3357,7 +3394,7 @@ MidiRegionView::patch_left (PatchChange *) void MidiRegionView::sysex_entered (SysEx* p) { - ostringstream s; + // ostringstream s; // CAIROCANVAS // need a way to extract text from p->_flag->_text // s << p->text(); @@ -3493,14 +3530,16 @@ MidiRegionView::selection_as_cut_buffer () const /** This method handles undo */ bool -MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx) +MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num) { 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; } + if (!(*m)->empty()) { + commit = true; + } paste_internal(pos, ctx.count, ctx.times, **m); } @@ -3508,7 +3547,10 @@ 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) { - if (a->second->paste(pos, selection, ctx)) { + if (a->second->paste(pos, selection, ctx, sub_num)) { + if(!commit) { + trackview.editor().begin_reversible_command (Operations::paste); + } commit = true; } } @@ -3535,23 +3577,23 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time const Evoral::Beats duration = last_time - first_time; const Evoral::Beats snap_duration = duration.snap_to(snap_beats); const Evoral::Beats paste_offset = snap_duration * paste_count; - const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset; + const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset; Evoral::Beats end_point = Evoral::Beats(); DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n", first_time, last_time, duration, pos, _region->position(), - pos_beats)); + quarter_note)); - clear_selection (); + clear_editor_note_selection (); for (int n = 0; n < (int) times; ++n) { for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) { boost::shared_ptr copied_note (new NoteType (*((*i).get()))); - copied_note->set_time (pos_beats + copied_note->time() - first_time); + copied_note->set_time (quarter_note + copied_note->time() - first_time); copied_note->set_id (Evoral::next_event_id()); /* make all newly added notes selected */ @@ -3571,7 +3613,8 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame)); _region->clear_changes (); - _region->set_length (end_frame - _region->position()); + /* we probably need to get the snap modifier somehow to make this correct for non-musical use */ + _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0)); trackview.session()->add_command (new StatefulDiffCommand (_region)); } @@ -3693,7 +3736,7 @@ MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_s } void -MidiRegionView::update_ghost_note (double x, double y) +MidiRegionView::update_ghost_note (double x, double y, uint32_t state) { x = std::max(0.0, x); @@ -3707,21 +3750,28 @@ MidiRegionView::update_ghost_note (double x, double y) PublicEditor& editor = trackview.editor (); framepos_t const unsnapped_frame = editor.pixel_to_sample (x); - framecnt_t grid_frames; - framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames); + + const int32_t divisions = editor.get_grid_music_divisions (state); + const bool shift_snap = midi_view()->note_mode() != Percussive; + const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap); + + /* ghost note may have been snapped before region */ + if (_ghost_note && snapped_beats.to_double() < 0.0) { + _ghost_note->hide(); + return; + + } else if (_ghost_note) { + _ghost_note->show(); + } /* calculate time in beats relative to start of source */ - const Evoral::Beats length = get_grid_beats(unsnapped_frame); - const Evoral::Beats time = std::max( - Evoral::Beats(), - absolute_frames_to_source_beats (f + _region->position ())); + const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position()); - _ghost_note->note()->set_time (time); + _ghost_note->note()->set_time (snapped_beats); _ghost_note->note()->set_length (length); _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y)); _ghost_note->note()->set_channel (mtv->get_channel_for_add ()); - _ghost_note->note()->set_velocity (get_velocity_for_add (time)); - + _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats)); /* the ghost note does not appear in ghost regions, so pass false in here */ update_note (_ghost_note, false); @@ -3729,7 +3779,7 @@ MidiRegionView::update_ghost_note (double x, double y) } void -MidiRegionView::create_ghost_note (double x, double y) +MidiRegionView::create_ghost_note (double x, double y, uint32_t state) { remove_ghost_note (); @@ -3741,7 +3791,7 @@ MidiRegionView::create_ghost_note (double x, double y) } _ghost_note->set_ignore_events (true); _ghost_note->set_outline_color (0x000000aa); - update_ghost_note (x, y); + update_ghost_note (x, y, state); _ghost_note->show (); show_verbose_cursor (_ghost_note->note ()); @@ -3771,7 +3821,7 @@ MidiRegionView::snap_changed () return; } - create_ghost_note (_last_ghost_x, _last_ghost_y); + create_ghost_note (_last_ghost_x, _last_ghost_y, 0); } void @@ -3833,9 +3883,6 @@ void MidiRegionView::enable_display (bool yn) { RegionView::enable_display (yn); - if (yn) { - redisplay_model (); - } } void @@ -3881,7 +3928,9 @@ MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats) _step_edit_cursor_width = beats; if (_step_edit_cursor) { - _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats))); + _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel ( + region_beats_to_region_frames (_step_edit_cursor_position + beats) + - region_beats_to_region_frames (_step_edit_cursor_position))); } } @@ -3949,21 +3998,14 @@ MidiRegionView::data_recorded (boost::weak_ptr w) void MidiRegionView::trim_front_starting () { - /* Reparent the note group to the region view's parent, so that it doesn't change - when the region view is trimmed. + /* We used to eparent the note group to the region view's parent, so that it didn't change. + now we update it. */ - _temporary_note_group = new ArdourCanvas::Container (group->parent ()); - _temporary_note_group->move (group->position ()); - _note_group->reparent (_temporary_note_group); } void MidiRegionView::trim_front_ending () { - _note_group->reparent (group); - delete _temporary_note_group; - _temporary_note_group = 0; - if (_region->start() < 0) { /* Trim drag made start time -ve; fix this */ midi_region()->fix_negative_start (); @@ -4003,11 +4045,10 @@ MidiRegionView::delete_sysex (SysEx* /*sysex*/) // display_sysexes(); } -void -MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const +std::string +MidiRegionView::get_note_name (boost::shared_ptr n, uint8_t note_value) const { using namespace MIDI::Name; - std::string name; MidiTimeAxisView* mtv = dynamic_cast(&trackview); @@ -4020,19 +4061,36 @@ MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const n->channel(), patch_key.bank(), patch_key.program(), - n->note()); + note_value); } - mtv->set_note_highlight (n->note()); } char buf[128]; snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d", - (int) n->note (), - name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(), + (int) note_value, + name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(), (int) n->channel() + 1, (int) n->velocity()); - show_verbose_cursor(buf, 10, 20); + return buf; +} + +void +MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr current_note, + uint8_t new_value) const +{ + MidiTimeAxisView* mtv = dynamic_cast(&trackview); + if (mtv) { + mtv->set_note_highlight (new_value); + } + + show_verbose_cursor(get_note_name(current_note, new_value), 10, 20); +} + +void +MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const +{ + show_verbose_cursor_for_new_note_value(n, n->note()); } void @@ -4071,40 +4129,30 @@ MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const } /** @param p A session framepos. - * @param grid_frames Filled in with the number of frames that a grid interval is at p. - * @return p snapped to the grid subdivision underneath it. + * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where + * bar is -1, 0 is audio samples and a positive integer is beat subdivisions. + * @return beat duration of p snapped to the grid subdivision underneath it. */ -framepos_t -MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const +Evoral::Beats +MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const { - PublicEditor& editor = trackview.editor (); - - const Evoral::Beats grid_beats = get_grid_beats(p); + TempoMap& map (trackview.session()->tempo_map()); + double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions); - grid_frames = region_beats_to_region_frames (grid_beats); - - /* Hack so that we always snap to the note that we are over, instead of snapping - to the next one if we're more than halfway through the one we're over. - */ - if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) { - p -= grid_frames / 2; - } - - return snap_frame_to_frame (p); -} - -/** Called when the selection has been cleared in any MidiRegionView. - * @param rv MidiRegionView that the selection was cleared in. - */ -void -MidiRegionView::selection_cleared (MidiRegionView* rv) -{ - if (rv == this) { - return; + if (divisions != 0 && shift_snap) { + const double qaf = map.quarter_note_at_frame (p + _region->position()); + /* Hack so that we always snap to the note that we are over, instead of snapping + to the next one if we're more than halfway through the one we're over. + */ + const Evoral::Beats grid_beats = get_grid_beats (p + _region->position()); + const double rem = eqaf - qaf; + if (rem >= 0.0) { + eqaf -= grid_beats.to_double(); + } } + const double session_start_off = _region->quarter_note() - midi_region()->start_beats(); - /* Clear our selection in sympathy; but don't signal the fact */ - clear_selection (false); + return Evoral::Beats (eqaf - session_start_off); } ChannelMode @@ -4127,7 +4175,7 @@ MidiRegionView::get_grid_beats(framepos_t pos) const { PublicEditor& editor = trackview.editor(); bool success = false; - Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos); + Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos); if (!success) { beats = Evoral::Beats(1); }