MidiRegionView::clear_events() does _selection.clear(). remove the extra one.
[ardour.git] / gtk2_ardour / midi_region_view.cc
index 0b13c3ff4e9b6f5c51355bb586cf773c1486dd9a..16569b6dce58f5a270cc36e024458a98f8de84d9 100644 (file)
@@ -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;
@@ -111,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)
@@ -123,7 +122,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
-       , _note_entered (false)
+       , _entered_note (0)
        , _mouse_changed_selection (false)
 {
        CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
@@ -155,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)
@@ -167,7 +165,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
-       , _note_entered (false)
+       , _entered_note (0)
        , _mouse_changed_selection (false)
 {
        CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
@@ -204,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)
@@ -216,7 +213,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
-       , _note_entered (false)
+       , _entered_note (0)
        , _mouse_changed_selection (false)
 {
        init (false);
@@ -237,7 +234,6 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<M
        , _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)
@@ -249,7 +245,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<M
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
-       , _note_entered (false)
+       , _entered_note (0)
        , _mouse_changed_selection (false)
 {
        init (true);
@@ -271,12 +267,12 @@ MidiRegionView::init (bool wfd)
 
        RegionView::init (false);
 
-       set_height (trackview.current_height());
+       //set_height (trackview.current_height());
 
        region_muted ();
        region_sync_changed ();
        region_resized (ARDOUR::bounds_change);
-       region_locked ();
+       //region_locked ();
 
        set_colors ();
 
@@ -395,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;
@@ -428,12 +424,12 @@ MidiRegionView::mouse_mode_changed ()
                        remove_ghost_note ();
 
                        /* XXX This is problematic as the function is executed for every region
-                       and only for one region _note_entered can be true. Still it's
+                       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 (!_note_entered) {
+                       if (!_entered_note) {
                                hide_verbose_cursor ();
                        }
                }
@@ -441,11 +437,11 @@ MidiRegionView::mouse_mode_changed ()
 }
 
 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,7 +464,7 @@ MidiRegionView::leave_internal()
 {
        hide_verbose_cursor ();
        remove_ghost_note ();
-       _note_entered = false;
+       _entered_note = 0;
 
        if (_grabbed_keyboard) {
                Keyboard::magic_widget_drop_focus();
@@ -501,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 *> (editor), group, this), (GdkEvent *) ev);
+                       } else {
+                               editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
+                       }
+
+                       _mouse_state = AddDragging;
+                       remove_ghost_note ();
+                       hide_verbose_cursor ();
+               } else {
+                       _mouse_state = Pressed;
+               }
 
                return true;
        }
@@ -536,7 +546,7 @@ MidiRegionView::button_release (GdkEventButton* ev)
 
                switch (editor.current_mouse_mode()) {
                case MouseRange:
-                       /* no motion occured - simple click */
+                       /* no motion occurred - simple click */
                        clear_editor_note_selection ();
                        _mouse_changed_selection = true;
                        break;
@@ -545,29 +555,13 @@ MidiRegionView::button_release (GdkEventButton* ev)
                case MouseTimeFX:
                        {
                                _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));
-                                       create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
-                               } else {
-                                       clear_editor_note_selection ();
-                               }
+                               clear_editor_note_selection ();
 
                                break;
                        }
                case MouseDraw:
-                       {
-                               Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
-                               create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
-                               break;
-                       }
+                       break;
+
                default:
                        break;
                }
@@ -576,8 +570,8 @@ MidiRegionView::button_release (GdkEventButton* ev)
                break;
 
        case AddDragging:
-               /* Only create a ghost note when we added a note, not when we were drag-selecting. */
-               create_ghost_note (ev->x, ev->y);
+               /* 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;
@@ -601,18 +595,23 @@ MidiRegionView::motion (GdkEventMotion* ev)
 {
        PublicEditor& editor = trackview.editor ();
 
-       if (!_note_entered) {
+       if (!_entered_note) {
 
-               if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
+               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())) {
 
-                       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) {
 
@@ -622,10 +621,10 @@ MidiRegionView::motion (GdkEventMotion* ev)
                } else if (editor.current_mouse_mode() == MouseDraw) {
 
                        if (_ghost_note) {
-                               update_ghost_note (ev->x, ev->y);
+                               update_ghost_note (ev->x, ev->y, ev->state);
                        }
                        else {
-                               create_ghost_note (ev->x, ev->y);
+                               create_ghost_note (ev->x, ev->y, ev->state);
                        }
                }
        }
@@ -643,13 +642,7 @@ 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 *> (&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 *> (&editor), this), (GdkEvent *) ev);
                                if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
                                        clear_editor_note_selection ();
@@ -727,11 +720,14 @@ 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) {
@@ -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<MidiTimeAxisView*>(&trackview);
        MidiStreamView* const   view = mtv->midi_view();
+       boost::shared_ptr<MidiRegion> mr  = boost::dynamic_pointer_cast<MidiRegion> (_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();
@@ -1119,12 +1115,12 @@ MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> 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;
                }
        }
@@ -1180,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<NoteType> 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 ();
@@ -1205,9 +1200,9 @@ MidiRegionView::redisplay_model()
                                cne = add_note (note, visible);
                        }
 
-                       set<boost::shared_ptr<NoteType> >::iterator it;
+                       set<Evoral::event_id_t>::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);
                                }
                        }
@@ -1388,34 +1383,38 @@ MidiRegionView::~MidiRegionView ()
        if (_active_notes) {
                end_write();
        }
-
-       _selection.clear();
+       _entered_note = 0;
        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
@@ -1480,44 +1479,7 @@ MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
        _current_range_min = min;
        _current_range_max = max;
 
-       for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
-               NoteBase* event = *i;
-               boost::shared_ptr<NoteType> note (event->note());
-
-               if (note->note() < _current_range_min ||
-                   note->note() > _current_range_max) {
-                       event->hide();
-               } else {
-                       event->show();
-               }
-
-               if (Note* cnote = dynamic_cast<Note*>(event)) {
-
-                       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);
-
-               } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
-                       update_hit (chit);
-               }
-       }
+       redisplay_model ();
 }
 
 GhostRegion*
@@ -1540,6 +1502,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv)
                ghost->add_note(*i);
        }
 
+       ghost->set_colors ();
        ghost->set_height ();
        ghost->set_duration (_region->length() / samples_per_pixel);
        ghosts.push_back (ghost);
@@ -1670,15 +1633,14 @@ MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > n
 bool
 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> 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<ARDOUR::MidiRegion> 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());
+       visible = (note->note() >= _current_range_min) &&
+               (note->note() <= _current_range_max);
 
        return !outside;
 }
@@ -1702,23 +1664,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<ARDOUR::MidiRegion> mr = midi_region();
        boost::shared_ptr<NoteType> 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(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 (std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1);
+               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 (std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1);
+               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(note_height()) - 1);
+
+       ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
 
        if (!note->length()) {
                if (_active_notes && note->note() < 128) {
@@ -1726,7 +1701,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;
@@ -1742,8 +1717,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<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
@@ -1760,10 +1736,12 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
 {
        boost::shared_ptr<NoteType> 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;
+       const double diamond_size = std::max(1., floor(note_height()) - 2.);
+       const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
 
        // see DnD note in MidiRegionView::apply_note_range() above
        if (y <= 0 || y >= _height) {
@@ -1776,14 +1754,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<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
                        MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
                        if (gr) {
-                               gr->update_note (ev);
+                               gr->update_hit (ev);
                        }
                }
        }
@@ -1810,7 +1789,7 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
 
        } else if (midi_view()->note_mode() == Percussive) {
 
-               const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
+               const double diamond_size = std::max(1., floor(note_height()) - 2.);
 
                Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
 
@@ -1868,7 +1847,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<MidiTimeAxisView*>(&trackview);
@@ -2091,6 +2071,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;
        }
@@ -2105,6 +2089,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) {
@@ -2116,6 +2104,7 @@ MidiRegionView::delete_selection()
        _selection.clear();
 
        apply_diff ();
+
        hide_verbose_cursor ();
 }
 
@@ -2209,13 +2198,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<boost::shared_ptr<NoteType> > notes)
+MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
 {
        NoteBase* cne;
-       list<boost::shared_ptr<NoteType> >::iterator n;
+       list<Evoral::event_id_t>::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);
@@ -2562,14 +2551,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;
                }
@@ -2727,6 +2717,7 @@ 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;
 
@@ -2788,9 +2779,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, ensure_snap) : 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()) {
@@ -2823,6 +2824,7 @@ 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;
@@ -2870,16 +2872,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, ensure_snap) + _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));
@@ -3228,7 +3234,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) {
 
@@ -3295,7 +3301,7 @@ MidiRegionView::change_channel(uint8_t channel)
 void
 MidiRegionView::note_entered(NoteBase* ev)
 {
-       _note_entered = true;
+       _entered_note = ev;
 
        Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
 
@@ -3318,7 +3324,7 @@ MidiRegionView::note_entered(NoteBase* ev)
 void
 MidiRegionView::note_left (NoteBase*)
 {
-       _note_entered = false;
+       _entered_note = 0;
 
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                (*i)->hide_velocity ();
@@ -3331,9 +3337,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();
@@ -3351,7 +3356,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();
@@ -3487,14 +3492,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);
        }
 
@@ -3502,7 +3509,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;
                }
        }
@@ -3529,14 +3539,14 @@ 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_editor_note_selection ();
 
@@ -3545,7 +3555,7 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time
                for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
 
                        boost::shared_ptr<NoteType> 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 */
@@ -3565,7 +3575,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));
        }
 
@@ -3687,7 +3698,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);
 
@@ -3701,21 +3712,35 @@ 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);
+
+       /* prevent Percussive mode from displaying a ghost hit at region end */
+       if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
+               _ghost_note->hide();
+               hide_verbose_cursor ();
+               return;
+       }
+
+       /* 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_note (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);
 
@@ -3723,7 +3748,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 ();
 
@@ -3735,7 +3760,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 ());
@@ -3765,7 +3790,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
@@ -3779,7 +3804,7 @@ MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, doub
 {
        /* XXX: This is dead code.  What was it for? */
 
-       double note = midi_stream_view()->y_to_note(y);
+       double note = y_to_note(y);
        Events e;
        MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
 
@@ -3827,9 +3852,6 @@ void
 MidiRegionView::enable_display (bool yn)
 {
        RegionView::enable_display (yn);
-       if (yn) {
-               redisplay_model ();
-       }
 }
 
 void
@@ -3875,7 +3897,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)));
        }
 }
 
@@ -3943,21 +3967,14 @@ MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> 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 ();
@@ -4020,7 +4037,7 @@ MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value
        char buf[128];
        snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
                  (int) note_value,
-                 name.empty() ? Evoral::midi_note_name (note_value).c_str() : name.c_str(),
+                 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
                  (int) n->channel() + 1,
                  (int) n->velocity());
 
@@ -4081,26 +4098,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);
-
-       grid_frames = region_beats_to_region_frames (grid_beats);
+       TempoMap& map (trackview.session()->tempo_map());
+       double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
 
-       /* 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;
+       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();
 
-       return snap_frame_to_frame (p);
+       return Evoral::Beats (eqaf - session_start_off);
 }
 
 ChannelMode
@@ -4123,9 +4144,30 @@ 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);
        }
        return beats;
 }
+uint8_t
+MidiRegionView::y_to_note (double y) const
+{
+       int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
+               + _current_range_min;
+
+       if (n < 0) {
+               return 0;
+       } else if (n > 127) {
+               return 127;
+       }
+
+       /* min due to rounding and/or off-by-one errors */
+       return min ((uint8_t) n, _current_range_max);
+}
+
+double
+MidiRegionView::note_to_y(uint8_t note) const
+{
+       return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
+}