mechanism to allow Track (or other Route-derived type) to add its own processors...
[ardour.git] / gtk2_ardour / midi_region_view.cc
index 987a9fff35ef0c40ace7f6ed160f3c190cf49e97..9d5a6f86e601e11d382c6640a030b0870f065efb 100644 (file)
@@ -41,7 +41,7 @@
 #include "ardour/session.h"
 
 #include "evoral/Parameter.hpp"
-#include "evoral/MIDIEvent.hpp"
+#include "evoral/Event.hpp"
 #include "evoral/Control.hpp"
 #include "evoral/midi_util.h"
 
@@ -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,10 +111,8 @@ 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)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -123,10 +121,14 @@ 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()));
+
+       _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
+       _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
+
        _note_group->raise_to_top();
        PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
 
@@ -155,10 +157,8 @@ 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)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -167,10 +167,14 @@ 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()));
+
+       _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
+       _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
+
        _note_group->raise_to_top();
 
        PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
@@ -185,6 +189,8 @@ MidiRegionView::parameter_changed (std::string const & p)
                if (_enable_display) {
                        redisplay_model();
                }
+       } else if (p == "color-regions-using-track-color") {
+               set_colors ();
        }
 }
 
@@ -204,10 +210,8 @@ 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)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -216,7 +220,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,10 +241,8 @@ 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)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -249,7 +251,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);
@@ -307,6 +309,7 @@ MidiRegionView::init (bool wfd)
                                                    gui_context ());
 
        Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
+       UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
        connect_to_diskstream ();
 }
 
@@ -395,7 +398,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 +431,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 +444,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 +471,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 +504,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;
        }
@@ -545,29 +562,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 +577,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 +602,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 +628,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 +649,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 +727,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 +923,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;
@@ -935,17 +938,9 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo
        }
 
        // Start of note in frames relative to region start
-       uint32_t divisions = 0;
-
-       if (snap_t) {
-               framecnt_t grid_frames;
-               t = snap_frame_to_grid_underneath (t, grid_frames);
-               divisions = trackview.editor().get_grid_music_divisions (0);
-       }
+       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 MidiModel::TimeType beat_time = Evoral::Beats (trackview.session()->tempo_map().exact_beat_at_frame (_region->position() + t, divisions)
-                                                            - (mr->beat() - mr->start_beats().to_double()));
        const double  note     = view->y_to_note(y);
        const uint8_t chan     = mtv->get_channel_for_add();
        const uint8_t velocity = get_velocity_for_add(beat_time);
@@ -961,7 +956,6 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo
 
        start_note_diff_command(_("add note"));
 
-       clear_editor_note_selection ();
        note_diff_add_note (new_note, true, false);
 
        apply_diff();
@@ -982,10 +976,8 @@ MidiRegionView::clear_events ()
                }
        }
 
-       for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               delete *i;
-       }
 
+       _note_group->clear (true);
        _events.clear();
        _patch_changes.clear();
        _sys_exes.clear();
@@ -1000,7 +992,7 @@ MidiRegionView::display_model(boost::shared_ptr<MidiModel> 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 ();
+       clear_events();
 
        if (_enable_display) {
                redisplay_model();
@@ -1059,7 +1051,7 @@ MidiRegionView::note_diff_add_change (NoteBase* ev,
 }
 
 void
-MidiRegionView::apply_diff (bool as_subcommand)
+MidiRegionView::apply_diff (bool as_subcommand, bool was_copy)
 {
        bool add_or_remove;
        bool commit = false;
@@ -1068,15 +1060,14 @@ MidiRegionView::apply_diff (bool as_subcommand)
                return;
        }
 
-       if ((add_or_remove = _note_diff_command->adds_or_removes())) {
+       if (!was_copy && (add_or_remove = _note_diff_command->adds_or_removes())) {
                // Mark all selected notes for selection when model reloads
                for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                        _marked_for_selection.insert((*i)->note());
                }
        }
 
-       midi_view()->midi_track()->midi_playlist()->region_edited(
-               _region, _note_diff_command);
+       midi_view()->midi_track()->midi_playlist()->region_edited (_region, _note_diff_command);
 
        if (as_subcommand) {
                _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
@@ -1102,24 +1093,25 @@ MidiRegionView::abort_command()
 {
        delete _note_diff_command;
        _note_diff_command = 0;
+       trackview.editor().abort_reversible_command();
        clear_editor_note_selection();
 }
 
 NoteBase*
 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
 {
+
        if (_optimization_iterator != _events.end()) {
                ++_optimization_iterator;
        }
 
-       if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
-               return *_optimization_iterator;
+       if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
+               return _optimization_iterator->second;
        }
 
-       for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
-               if ((*_optimization_iterator)->note() == note) {
-                       return *_optimization_iterator;
-               }
+       _optimization_iterator = _events.find (note);
+       if (_optimization_iterator != _events.end()) {
+               return _optimization_iterator->second;
        }
 
        return 0;
@@ -1127,19 +1119,43 @@ 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) {
-                       return *it;
+               if (it->first->id() == id) {
+                       return it->second;
                }
        }
 
        return 0;
 }
 
+boost::shared_ptr<PatchChange>
+MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
+{
+       PatchChanges::const_iterator f = _patch_changes.find (p);
+
+       if (f != _patch_changes.end()) {
+               return f->second;
+       }
+
+       return boost::shared_ptr<PatchChange>();
+}
+
+boost::shared_ptr<SysEx>
+MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
+{
+       SysExes::const_iterator f = _sys_exes.find (s);
+
+       if (f != _sys_exes.end()) {
+               return f->second;
+       }
+
+       return boost::shared_ptr<SysEx>();
+}
+
 void
 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
 {
@@ -1149,7 +1165,7 @@ MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOper
        for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
                NoteBase* cne = find_canvas_note (*n);
                if (cne) {
-                       e.push_back (cne);
+                       e.insert (make_pair (*n, cne));
                }
        }
 }
@@ -1165,8 +1181,8 @@ MidiRegionView::redisplay_model()
                           touching model.  Leave active notes (with length 0) alone since
                           they are being extended. */
                        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-                               if ((*i)->note()->length() > 0) {
-                                       update_note(*i);
+                               if (i->second->note()->length() > 0) {
+                                       update_note(i->second);
                                }
                        }
                        _last_display_zoom = zoom;
@@ -1178,80 +1194,107 @@ MidiRegionView::redisplay_model()
                return;
        }
 
-       for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               (*i)->invalidate ();
+       for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
+               _optimization_iterator->second->invalidate();
        }
 
-       MidiModel::ReadLock lock(_model->read_lock());
+       bool empty_when_starting = _events.empty();
+       _optimization_iterator = _events.begin();
+       MidiModel::Notes missing_notes;
+       Note* sus = NULL;
+       Hit*  hit = NULL;
 
+       MidiModel::ReadLock lock(_model->read_lock());
        MidiModel::Notes& notes (_model->notes());
-       _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);
                bool visible;
 
                if (note_in_region_range (note, visible)) {
-
                        if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
-
+                               cne->validate ();
                                if (visible) {
-                                       cne->validate ();
-                                       update_note (cne);
                                        cne->show ();
                                } else {
                                        cne->hide ();
                                }
-
                        } else {
-
-                               cne = add_note (note, visible);
-                       }
-
-                       set<boost::shared_ptr<NoteType> >::iterator it;
-                       for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
-                               if (*(*it) == *note) {
-                                       add_to_selection (cne);
-                               }
-                       }
-
-               } else {
-
-                       if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
-                               cne->validate ();
-                               cne->hide ();
+                               missing_notes.insert (note);
                        }
                }
        }
 
-       /* remove note items that are no longer valid */
-
        if (!empty_when_starting) {
+               MidiModel::Notes::iterator f;
                for (Events::iterator i = _events.begin(); i != _events.end(); ) {
-                       if (!(*i)->valid ()) {
+
+                       NoteBase* cne = i->second;
+
+                       /* remove note items that are no longer valid */
+                       if (!cne->valid()) {
 
                                for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
                                        MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
                                        if (gr) {
-                                               gr->remove_note (*i);
+                                               gr->remove_note (cne);
                                        }
                                }
 
-                               delete *i;
+                               delete cne;
                                i = _events.erase (i);
 
                        } else {
+                               bool visible = cne->item()->visible();
+
+                               if ((sus = dynamic_cast<Note*>(cne))) {
+
+                                       if (visible) {
+                                               update_sustained (sus);
+                                       }
+
+                               } else if ((hit = dynamic_cast<Hit*>(cne))) {
+
+                                       if (visible) {
+                                               update_hit (hit);
+                                       }
+
+                               }
                                ++i;
                        }
                }
        }
 
-       _patch_changes.clear();
-       _sys_exes.clear();
+       for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
+               boost::shared_ptr<NoteType> note (*n);
+               NoteBase* cne;
+               bool visible;
+
+               if (note_in_region_range (note, visible)) {
+                       if (visible) {
+                               cne = add_note (note, true);
+                       } else {
+                               cne = add_note (note, false);
+                       }
+               } else {
+                       cne = add_note (note, false);
+               }
+
+               for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
+                       if ((*it) == note->id()) {
+                               add_to_selection (cne);
+                       }
+               }
+       }
+
+       for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
+               MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
+               if (gr && !gr->trackview.hidden()) {
+                       gr->redisplay_model ();
+               }
+       }
 
        display_sysexes();
        display_patch_changes ();
@@ -1260,12 +1303,6 @@ MidiRegionView::redisplay_model()
        _marked_for_velocity.clear ();
        _pending_note_selection.clear ();
 
-       /* we may have caused _events to contain things out of order (e.g. if a note
-          moved earlier or later). we don't generally need them in time order, but
-          make a note that a sort is required for those cases that require it.
-       */
-
-       _sort_needed = true;
 }
 
 void
@@ -1286,13 +1323,31 @@ void
 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
 {
        for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
+               boost::shared_ptr<PatchChange> p;
 
                if ((*i)->channel() != channel) {
                        continue;
                }
 
-               const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
-               add_canvas_patch_change (*i, patch_name, active_channel);
+               if ((p = find_canvas_patch_change (*i)) != 0) {
+
+                       const framecnt_t region_frames = source_beats_to_region_frames ((*i)->time());
+
+                       if (region_frames < 0 || region_frames >= _region->length()) {
+                               p->hide();
+                       } else {
+                               const double x = trackview.editor().sample_to_pixel (region_frames);
+                               const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
+                               p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
+                               p->set_text (patch_name);
+
+                               p->show();
+                       }
+
+               } else {
+                       const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
+                       add_canvas_patch_change (*i, patch_name, active_channel);
+               }
        }
 }
 
@@ -1305,14 +1360,9 @@ MidiRegionView::display_sysexes()
        if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
 
                for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
-                       const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
-                               boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
-
-                       if (mev) {
-                               if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
-                                       have_periodic_system_messages = true;
-                                       break;
-                               }
+                       if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
+                               have_periodic_system_messages = true;
+                               break;
                        }
                }
 
@@ -1337,17 +1387,12 @@ MidiRegionView::display_sysexes()
        }
 
        for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
+               MidiModel::SysExPtr sysex_ptr = *i;
+               Evoral::Beats time = sysex_ptr->time();
 
-               const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
-                       boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
-
-               Evoral::Beats time = (*i)->time();
-
-               if (mev) {
-                       if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
-                               if (!display_periodic_messages) {
-                                       continue;
-                               }
+               if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
+                       if (!display_periodic_messages) {
+                               continue;
                        }
                }
 
@@ -1367,9 +1412,16 @@ MidiRegionView::display_sysexes()
 
                // CAIROCANVAS: no longer passing *i (the sysex event) to the
                // SysEx canvas object!!!
+               boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
 
-               boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
-                       new SysEx (*this, _note_group, text, height, x, 1.0));
+               if (!sysex) {
+                       sysex = boost::shared_ptr<SysEx>(
+                               new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
+                       _sys_exes.insert (make_pair (sysex_ptr, sysex));
+               } else {
+                       sysex->set_height (height);
+                       sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
+               }
 
                // Show unless message is beyond the region bounds
                if (time - _region->start() >= _region->length() || time < _region->start()) {
@@ -1377,8 +1429,6 @@ MidiRegionView::display_sysexes()
                } else {
                        sysex->show();
                }
-
-               _sys_exes.push_back(sysex);
        }
 }
 
@@ -1395,14 +1445,12 @@ 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
@@ -1421,10 +1469,13 @@ MidiRegionView::region_resized (const PropertyChange& what_changed)
            what_changed.contains (ARDOUR::Properties::position)) {
                _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
        }
-       /* catch an end trim so we can live update */
+       /* 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);
        }
 }
 
@@ -1435,13 +1486,17 @@ MidiRegionView::reset_width_dependent_items (double pixel_width)
 
        if (_enable_display) {
                redisplay_model();
-               }
+       }
 
-       for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
-               if ((*x)->canvas_item()->width() >= _pixel_width) {
-                       (*x)->hide();
-               } else {
-                       (*x)->show();
+       bool hide_all = false;
+       PatchChanges::iterator x = _patch_changes.begin();
+       if (x != _patch_changes.end()) {
+               hide_all = x->second->width() >= _pixel_width;
+       }
+
+       if (hide_all) {
+               for (; x != _patch_changes.end(); ++x) {
+                       x->second->hide();
                }
        }
 
@@ -1464,7 +1519,7 @@ MidiRegionView::set_height (double height)
        }
 
        for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
-               (*x)->set_height (midi_stream_view()->contents_height());
+               (*x).second->set_height (midi_stream_view()->contents_height());
        }
 
        if (_step_edit_cursor) {
@@ -1490,44 +1545,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*
@@ -1546,14 +1564,16 @@ MidiRegionView::add_ghost (TimeAxisView& tv)
                ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
        }
 
+       ghost->set_colors ();
+       ghost->set_height ();
+       ghost->set_duration (_region->length() / samples_per_pixel);
+
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               ghost->add_note(*i);
+               ghost->add_note(i->second);
        }
 
-       ghost->set_height ();
-       ghost->set_duration (_region->length() / samples_per_pixel);
        ghosts.push_back (ghost);
-
+       enable_display (true);
        return ghost;
 }
 
@@ -1681,11 +1701,13 @@ bool
 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
 {
        const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
-       const bool outside = (note->time() < midi_reg->start_beats() ||
-                             note->time() > midi_reg->start_beats() + midi_reg->length_beats());
 
-       visible = (note->note() >= midi_stream_view()->lowest_note()) &&
-               (note->note() <= midi_stream_view()->highest_note());
+       /* 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() >= _current_range_min) &&
+               (note->note() <= _current_range_max);
 
        return !outside;
 }
@@ -1712,33 +1734,33 @@ 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 framepos_t note_start_frames = map.frame_at_beat (_region->beat() - mr->start_beats().to_double()
-                                                               + note->time().to_double()) - _region->position();
+
+       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 */
+       const double y0 = 1 + floor(note_to_y(note->note()));
+       double y1;
 
-       if (note->length() > 0) {
-               Evoral::Beats note_end_time = note->end_time();
+       /* 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->end_time() > mr->start_beats() + mr->length_beats()) {
+               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_beat (_region->beat() - mr->start_beats().to_double()
-                                                                     + note_end_time.to_double()) - _region->position();
+               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 {
                x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
        }
 
-       y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1);
+       y1 = y0 + std::max(1., floor(note_height()) - 1);
 
-       ArdourCanvas::Rect rect (x0, y0, x1, y1);
-       ev->set (rect);
+       ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
 
        if (!note->length()) {
                if (_active_notes && note->note() < 128) {
@@ -1762,18 +1784,10 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
        }
 
        // Update color in case velocity has changed
-       //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);
-                       }
-               }
-       }
+       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()));
+
 }
 
 void
@@ -1781,11 +1795,12 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
 {
        boost::shared_ptr<NoteType> note = ev->note();
 
-       const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_beat (_region->beat() - midi_region()->start_beats().to_double()
-                                                                                                + note->time().to_double()) - _region->position();
+       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) {
@@ -1798,17 +1813,10 @@ 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()));
-
-       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);
-                       }
-               }
-       }
+       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()));
+
 }
 
 /** Add a MIDI note to the view (with length).
@@ -1824,7 +1832,7 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
 
        if (midi_view()->note_mode() == Sustained) {
 
-               Note* ev_rect = new Note (*this, _note_group, note);
+               Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
 
                update_sustained (ev_rect);
 
@@ -1832,9 +1840,9 @@ 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);
+               Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
 
                update_hit (ev_diamond);
 
@@ -1862,7 +1870,7 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
                }
 
                event->on_channel_selection_change (get_selected_channels());
-               _events.push_back(event);
+               _events.insert (make_pair (event->note(), event));
 
                if (visible) {
                        event->show();
@@ -1934,14 +1942,16 @@ MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const
        // so we need to do something more sophisticated to keep its color
        // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
        // up to date.
-
        boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
                new PatchChange(*this, group,
                                displaytext,
                                height,
                                x, 1.0,
                                instrument_info(),
-                               patch));
+                               patch,
+                               _patch_change_outline,
+                               _patch_change_fill)
+               );
 
        if (patch_change->item().width() < _pixel_width) {
                // Show unless patch change is beyond the region bounds
@@ -1954,7 +1964,19 @@ MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const
                patch_change->hide ();
        }
 
-       _patch_changes.push_back (patch_change);
+       _patch_changes.insert (make_pair (patch, patch_change));
+}
+
+void
+MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
+{
+       /* remove the canvas item */
+       for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
+               if (x->second->patch() == pc->patch()) {
+                       _patch_changes.erase (x);
+                       break;
+               }
+       }
 }
 
 MIDI::Name::PatchPrimaryKey
@@ -1997,6 +2019,7 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri
 {
        string name = _("alter patch change");
        trackview.editor().begin_reversible_command (name);
+
        MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
 
        if (pc.patch()->program() != new_patch.program()) {
@@ -2011,7 +2034,7 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri
        _model->apply_command (*trackview.session(), c);
        trackview.editor().commit_reversible_command ();
 
-       _patch_changes.clear ();
+       remove_canvas_patch_change (&pc);
        display_patch_changes ();
 }
 
@@ -2041,7 +2064,13 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const
        _model->apply_command (*trackview.session(), c);
        trackview.editor().commit_reversible_command ();
 
-       _patch_changes.clear ();
+       for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
+               if (x->second->patch() == old_change) {
+                       _patch_changes.erase (x);
+                       break;
+               }
+       }
+
        display_patch_changes ();
 }
 
@@ -2053,7 +2082,6 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const
 void
 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
 {
-       MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
        string name = _("add patch change");
 
        trackview.editor().begin_reversible_command (name);
@@ -2061,7 +2089,7 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beat
        c->add (MidiModel::PatchChangePtr (
                        new Evoral::PatchChange<Evoral::Beats> (
                                absolute_frames_to_source_beats (_region->position() + t),
-                               mtv->get_channel_for_add(), patch.program(), patch.bank()
+                               patch.channel(), patch.program(), patch.bank()
                                )
                        )
                );
@@ -2069,7 +2097,6 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beat
        _model->apply_command (*trackview.session(), c);
        trackview.editor().commit_reversible_command ();
 
-       _patch_changes.clear ();
        display_patch_changes ();
 }
 
@@ -2082,7 +2109,6 @@ MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
        _model->apply_command (*trackview.session(), c);
        trackview.editor().commit_reversible_command ();
 
-       _patch_changes.clear ();
        display_patch_changes ();
 }
 
@@ -2090,12 +2116,13 @@ void
 MidiRegionView::delete_patch_change (PatchChange* pc)
 {
        trackview.editor().begin_reversible_command (_("delete patch change"));
+
        MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
        c->remove (pc->patch ());
        _model->apply_command (*trackview.session(), c);
        trackview.editor().commit_reversible_command ();
 
-       _patch_changes.clear ();
+       remove_canvas_patch_change (pc);
        display_patch_changes ();
 }
 
@@ -2114,6 +2141,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;
        }
@@ -2128,6 +2159,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) {
@@ -2139,6 +2174,7 @@ MidiRegionView::delete_selection()
        _selection.clear();
 
        apply_diff ();
+
        hide_verbose_cursor ();
 }
 
@@ -2199,7 +2235,7 @@ MidiRegionView::select_all_notes ()
        clear_editor_note_selection ();
 
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               add_to_selection (*i);
+               add_to_selection (i->second);
        }
 }
 
@@ -2209,9 +2245,9 @@ MidiRegionView::select_range (framepos_t start, framepos_t end)
        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());
+               framepos_t t = source_beats_to_absolute_frames(i->first->time());
                if (t >= start && t <= end) {
-                       add_to_selection (*i);
+                       add_to_selection (i->second);
                }
        }
 }
@@ -2220,10 +2256,10 @@ void
 MidiRegionView::invert_selection ()
 {
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               if ((*i)->selected()) {
-                       remove_from_selection(*i);
+               if (i->second->selected()) {
+                       remove_from_selection(i->second);
                } else {
-                       add_to_selection (*i);
+                       add_to_selection (i->second);
                }
        }
 }
@@ -2232,13 +2268,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);
@@ -2383,11 +2419,10 @@ MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
 
                        /* find notes entirely within OR spanning the earliest..latest range */
 
-                       if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
-                           ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
-                               add_to_selection (*i);
+                       if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
+                           (i->first->time() <= earliest && i->first->end_time() >= latest)) {
+                               add_to_selection (i->second);
                        }
-
                }
        }
 }
@@ -2416,14 +2451,14 @@ MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double g
        // We probably need a tree to be able to find events in O(log(n)) time.
 
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
+               if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
                        // Rectangles intersect
-                       if (!(*i)->selected()) {
-                               add_to_selection (*i);
+                       if (!i->second->selected()) {
+                               add_to_selection (i->second);
                        }
-               } else if ((*i)->selected() && !extend) {
+               } else if (i->second->selected() && !extend) {
                        // Rectangles do not intersect
-                       remove_from_selection (*i);
+                       remove_from_selection (i->second);
                }
        }
 
@@ -2458,13 +2493,13 @@ MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool exten
        // We probably need a tree to be able to find events in O(log(n)) time.
 
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
+               if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
                        // within y- (note-) range
-                       if (!(*i)->selected()) {
-                               add_to_selection (*i);
+                       if (!i->second->selected()) {
+                               add_to_selection (i->second);
                        }
-               } else if ((*i)->selected() && !extend) {
-                       remove_from_selection (*i);
+               } else if (i->second->selected() && !extend) {
+                       remove_from_selection (i->second);
                }
        }
 }
@@ -2513,11 +2548,9 @@ MidiRegionView::add_to_selection (NoteBase* ev)
        }
 }
 
-void
-MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
+Evoral::Beats
+MidiRegionView::earliest_in_selection ()
 {
-       typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
-       PossibleChord to_play;
        Evoral::Beats earliest = Evoral::MaxBeats;
 
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
@@ -2526,11 +2559,47 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
                }
        }
 
+       return earliest;
+}
+
+void
+MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
+{
+       typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
+       Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
+       TempoMap& tmap (editor->session()->tempo_map());
+       PossibleChord to_play;
+       Evoral::Beats earliest = earliest_in_selection();
+
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
-               if ((*i)->note()->time() == earliest) {
-                       to_play.push_back ((*i)->note());
+               NoteBase* n = *i;
+               if (n->note()->time() == earliest) {
+                       to_play.push_back (n->note());
+               }
+               double const note_time_qn = session_relative_qn (n->note()->time().to_double());
+               double dx = 0.0;
+               if (midi_view()->note_mode() == Sustained) {
+                       dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                               - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
+               } else {
+                       /* Hit::x0() is offset by _position.x, unlike Note::x0() */
+                       Hit* hit = dynamic_cast<Hit*>(n);
+                       if (hit) {
+                               dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                                       - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
+                       }
                }
+
                (*i)->move_event(dx, dy);
+
+               /* update length */
+               if (midi_view()->note_mode() == Sustained) {
+                       Note* sus = dynamic_cast<Note*> (*i);
+                       double const len_dx = editor->sample_to_pixel_unrounded (
+                               tmap.frame_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
+
+                       sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
+               }
        }
 
        if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
@@ -2556,67 +2625,210 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
        }
 }
 
+NoteBase*
+MidiRegionView::copy_selection (NoteBase* primary)
+{
+       _copy_drag_events.clear ();
+
+       if (_selection.empty()) {
+               return 0;
+       }
+
+       NoteBase* note;
+       NoteBase* ret = 0;
+
+       for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+               boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
+               if (midi_view()->note_mode() == Sustained) {
+                       Note* n = new Note (*this, _note_group, g);
+                       update_sustained (n, false);
+                       note = n;
+               } else {
+                       Hit* h = new Hit (*this, _note_group, 10, g);
+                       update_hit (h, false);
+                       note = h;
+               }
+
+               if ((*i) == primary) {
+                       ret = note;
+               }
+
+               _copy_drag_events.push_back (note);
+       }
+
+       return ret;
+}
+
+void
+MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
+{
+       typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
+       Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
+       TempoMap& tmap (editor->session()->tempo_map());
+       PossibleChord to_play;
+       Evoral::Beats earliest = earliest_in_selection();
+
+       for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
+               NoteBase* n = *i;
+               if (n->note()->time() == earliest) {
+                       to_play.push_back (n->note());
+               }
+               double const note_time_qn = session_relative_qn (n->note()->time().to_double());
+               double dx = 0.0;
+               if (midi_view()->note_mode() == Sustained) {
+                       dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                               - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
+               } else {
+                       Hit* hit = dynamic_cast<Hit*>(n);
+                       if (hit) {
+                               dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                                       - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
+                       }
+               }
+
+               (*i)->move_event(dx, dy);
+
+               if (midi_view()->note_mode() == Sustained) {
+                       Note* sus = dynamic_cast<Note*> (*i);
+                       double const len_dx = editor->sample_to_pixel_unrounded (
+                               tmap.frame_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
+
+                       sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
+               }
+       }
+
+       if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
+
+               if (to_play.size() > 1) {
+
+                       PossibleChord shifted;
+
+                       for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
+                               boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
+                               moved_note->set_note (moved_note->note() + cumulative_dy);
+                               shifted.push_back (moved_note);
+                       }
+
+                       start_playing_midi_chord (shifted);
+
+               } else if (!to_play.empty()) {
+
+                       boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
+                       moved_note->set_note (moved_note->note() + cumulative_dy);
+                       start_playing_midi_note (moved_note);
+               }
+       }
+}
+
 void
-MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
+MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
 {
        uint8_t lowest_note_in_selection  = 127;
        uint8_t highest_note_in_selection = 0;
        uint8_t highest_note_difference   = 0;
 
-       // find highest and lowest notes first
+       if (!copy) {
+               // find highest and lowest notes first
 
-       for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
-               uint8_t pitch = (*i)->note()->note();
-               lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
-               highest_note_in_selection = std::max(highest_note_in_selection, pitch);
-       }
-
-       /*
-         cerr << "dnote: " << (int) dnote << endl;
-         cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
-         << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
-         cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
-         << int(highest_note_in_selection) << endl;
-         cerr << "selection size: " << _selection.size() << endl;
-         cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
-       */
+               for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+                       uint8_t pitch = (*i)->note()->note();
+                       lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
+                       highest_note_in_selection = std::max(highest_note_in_selection, pitch);
+               }
 
-       // Make sure the note pitch does not exceed the MIDI standard range
-       if (highest_note_in_selection + dnote > 127) {
-               highest_note_difference = highest_note_in_selection - 127;
-       }
+               /*
+                 cerr << "dnote: " << (int) dnote << endl;
+                 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
+                 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
+                 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
+                 << int(highest_note_in_selection) << endl;
+                 cerr << "selection size: " << _selection.size() << endl;
+                 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
+               */
 
-       start_note_diff_command (_("move notes"));
+               // Make sure the note pitch does not exceed the MIDI standard range
+               if (highest_note_in_selection + dnote > 127) {
+                       highest_note_difference = highest_note_in_selection - 127;
+               }
 
-       for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
+               start_note_diff_command (_("move notes"));
 
-               framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
-               Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
+               for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
 
-               if (new_time < 0) {
-                       continue;
+                       Evoral::Beats new_time = Evoral::Beats ((*i)->note()->time().to_double() + d_qn);
+
+                       if (new_time < 0) {
+                               continue;
+                       }
+
+                       note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
+
+                       uint8_t original_pitch = (*i)->note()->note();
+                       uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
+
+                       // keep notes in standard midi range
+                       clamp_to_0_127(new_pitch);
+
+                       lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
+                       highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+
+                       note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
+               }
+       } else {
+
+               clear_editor_note_selection ();
+
+               for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
+                       uint8_t pitch = (*i)->note()->note();
+                       lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
+                       highest_note_in_selection = std::max(highest_note_in_selection, pitch);
+               }
+
+               // Make sure the note pitch does not exceed the MIDI standard range
+               if (highest_note_in_selection + dnote > 127) {
+                       highest_note_difference = highest_note_in_selection - 127;
                }
 
-               note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
+               start_note_diff_command (_("copy notes"));
 
-               uint8_t original_pitch = (*i)->note()->note();
-               uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
+               for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
 
-               // keep notes in standard midi range
-               clamp_to_0_127(new_pitch);
+                       /* update time */
+                       Evoral::Beats new_time = Evoral::Beats ((*i)->note()->time().to_double() + d_qn);
 
-               lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
-               highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+                       if (new_time < 0) {
+                               continue;
+                       }
+
+                       (*i)->note()->set_time (new_time);
+
+                       /* update pitch */
+
+                       uint8_t original_pitch = (*i)->note()->note();
+                       uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
+
+                       (*i)->note()->set_note (new_pitch);
+
+                       // keep notes in standard midi range
+                       clamp_to_0_127(new_pitch);
 
-               note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
+                       lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
+                       highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+
+                       note_diff_add_note ((*i)->note(), true);
+
+                       delete *i;
+               }
+
+               _copy_drag_events.clear ();
        }
 
-       apply_diff();
+       apply_diff (false, copy);
 
        // care about notes being moved beyond the upper/lower bounds on the canvas
        if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
            highest_note_in_selection > midi_stream_view()->highest_note()) {
-               midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
+               midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
        }
 }
 
@@ -2629,7 +2841,7 @@ framepos_t
 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
 {
        PublicEditor& editor (trackview.editor());
-       return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
+       return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap).frame;
 }
 
 /** @param x Pixel relative to the region position.
@@ -2813,7 +3025,7 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_
                        }
 
                        double  snapped_x;
-                       uint32_t divisions = 0;
+                       int32_t divisions = 0;
 
                        if (with_snap) {
                                snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
@@ -2916,8 +3128,9 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_
                }
 
                /* and then to beats */
-               const Evoral::Beats x_beats = Evoral::Beats (tmap.exact_beat_at_frame (current_fr + midi_region()->position(), divisions)
-                                                            - midi_region()->beat()) + midi_region()->start_beats();
+               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));
@@ -3280,21 +3493,20 @@ MidiRegionView::nudge_notes (bool forward, bool fine)
 
                /* use grid */
 
-               framepos_t next_pos = ref_point;
-
+               MusicFrame next_pos (ref_point, 0);
                if (forward) {
-                       if (max_framepos - 1 < next_pos) {
-                               next_pos += 1;
+                       if (max_framepos - 1 < next_pos.frame) {
+                               next_pos.frame += 1;
                        }
                } else {
-                       if (next_pos == 0) {
+                       if (next_pos.frame == 0) {
                                return;
                        }
-                       next_pos -= 1;
+                       next_pos.frame -= 1;
                }
 
                trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
-               const framecnt_t distance = ref_point - next_pos;
+               const framecnt_t distance = ref_point - next_pos.frame;
                delta = region_frames_to_region_beats (fabs ((double)distance));
        }
 
@@ -3333,7 +3545,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());
 
@@ -3356,7 +3568,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 ();
@@ -3388,7 +3600,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();
@@ -3452,7 +3664,7 @@ MidiRegionView::midi_channel_mode_changed ()
 
        // Update notes for selection
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               (*i)->on_channel_selection_change (mask);
+               i->second->on_channel_selection_change (mask);
        }
 
        _patch_changes.clear ();
@@ -3531,7 +3743,9 @@ MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContex
        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);
        }
 
@@ -3540,6 +3754,9 @@ MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContex
        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, sub_num)) {
+                       if(!commit) {
+                               trackview.editor().begin_reversible_command (Operations::paste);
+                       }
                        commit = true;
                }
        }
@@ -3566,14 +3783,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 ();
 
@@ -3582,7 +3799,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 */
@@ -3616,53 +3833,47 @@ struct EventNoteTimeEarlyFirstComparator {
        }
 };
 
-void
-MidiRegionView::time_sort_events ()
-{
-       if (!_sort_needed) {
-               return;
-       }
-
-       EventNoteTimeEarlyFirstComparator cmp;
-       _events.sort (cmp);
-
-       _sort_needed = false;
-}
-
 void
 MidiRegionView::goto_next_note (bool add_to_selection)
 {
        bool use_next = false;
 
-       if (_events.back()->selected()) {
-               return;
-       }
-
-       time_sort_events ();
-
        MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
        uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
+       NoteBase* first_note = 0;
 
-       for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               if ((*i)->selected()) {
-                       use_next = true;
-                       continue;
-               } else if (use_next) {
-                       if (channel_mask & (1 << (*i)->note()->channel())) {
-                               if (!add_to_selection) {
-                                       unique_select (*i);
-                               } else {
-                                       note_selected (*i, true, false);
+       MidiModel::ReadLock lock(_model->read_lock());
+       MidiModel::Notes& notes (_model->notes());
+
+       for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
+               NoteBase* cne = 0;
+               if ((cne = find_canvas_note (*n))) {
+
+                       if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
+                               first_note = cne;
+                       }
+
+                       if (cne->selected()) {
+                               use_next = true;
+                               continue;
+                       } else if (use_next) {
+                               if (channel_mask & (1 << (*n)->channel())) {
+                                       if (!add_to_selection) {
+                                               unique_select (cne);
+                                       } else {
+                                               note_selected (cne, true, false);
+                                       }
+
+                                       return;
                                }
-                               return;
                        }
                }
        }
 
        /* use the first one */
 
-       if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
-               unique_select (_events.front());
+       if (!_events.empty() && first_note) {
+               unique_select (first_note);
        }
 }
 
@@ -3671,35 +3882,43 @@ MidiRegionView::goto_previous_note (bool add_to_selection)
 {
        bool use_next = false;
 
-       if (_events.front()->selected()) {
-               return;
-       }
-
-       time_sort_events ();
-
        MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
        uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
+       NoteBase* last_note = 0;
 
-       for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
-               if ((*i)->selected()) {
-                       use_next = true;
-                       continue;
-               } else if (use_next) {
-                       if (channel_mask & (1 << (*i)->note()->channel())) {
-                               if (!add_to_selection) {
-                                       unique_select (*i);
-                               } else {
-                                       note_selected (*i, true, false);
+       MidiModel::ReadLock lock(_model->read_lock());
+       MidiModel::Notes& notes (_model->notes());
+
+       for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
+               NoteBase* cne = 0;
+               if ((cne = find_canvas_note (*n))) {
+
+                       if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
+                               last_note = cne;
+                       }
+
+                       if (cne->selected()) {
+                               use_next = true;
+                               continue;
+
+                       } else if (use_next) {
+                               if (channel_mask & (1 << (*n)->channel())) {
+                                       if (!add_to_selection) {
+                                               unique_select (cne);
+                                       } else {
+                                               note_selected (cne, true, false);
+                                       }
+
+                                       return;
                                }
-                               return;
                        }
                }
        }
 
        /* use the last one */
 
-       if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
-               unique_select (*(_events.rbegin()));
+       if (!_events.empty() && last_note) {
+               unique_select (last_note);
        }
 }
 
@@ -3708,24 +3927,24 @@ MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_s
 {
        bool had_selected = false;
 
-       time_sort_events ();
+       /* we previously time sorted events here, but Notes is a multiset sorted by time */
 
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               if ((*i)->selected()) {
-                       selected.insert ((*i)->note());
+               if (i->second->selected()) {
+                       selected.insert (i->first);
                        had_selected = true;
                }
        }
 
        if (allow_all_if_none_selected && !had_selected) {
                for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-                       selected.insert ((*i)->note());
+                       selected.insert (i->first);
                }
        }
 }
 
 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);
 
@@ -3739,21 +3958,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);
 
@@ -3761,7 +3994,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 ();
 
@@ -3773,7 +4006,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 ());
@@ -3803,7 +4036,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
@@ -3817,7 +4050,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);
 
@@ -3838,8 +4071,8 @@ MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, doub
        }
 
        for (Events::iterator i = e.begin(); i != e.end(); ++i) {
-               if (_selection.insert (*i).second) {
-                       (*i)->set_selected (true);
+               if (_selection.insert (i->second).second) {
+                       i->second->set_selected (true);
                }
        }
 
@@ -3854,8 +4087,11 @@ MidiRegionView::color_handler ()
 {
        RegionView::color_handler ();
 
+       _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
+       _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
+
        for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               (*i)->set_selected ((*i)->selected()); // will change color
+               i->second->set_selected (i->second->selected()); // will change color
        }
 
        /* XXX probably more to do here */
@@ -3940,7 +4176,7 @@ MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
        framepos_t back = max_framepos;
 
        for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
-               Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
+               const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
 
                if (ev.is_channel_event()) {
                        if (get_channel_mode() == FilterChannels) {
@@ -3980,29 +4216,18 @@ 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 ();
        }
-       /* until _start is modified on the fly during front trim,
-          we have to redisplay the model when a start trim has finished.
-       */
-       enable_display (true);
 }
 
 void
@@ -4061,7 +4286,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());
 
@@ -4122,26 +4347,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);
-       const Evoral::Beats p_beat = max (Evoral::Beats(), region_frames_to_region_beats (p));
-
-       grid_frames = region_beats_to_region_frames (p_beat + grid_beats) - region_beats_to_region_frames (p_beat);
+       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
@@ -4170,3 +4399,30 @@ MidiRegionView::get_grid_beats(framepos_t pos) const
        }
        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;
+}
+
+double
+MidiRegionView::session_relative_qn (double qn) const
+{
+       return qn + (region()->quarter_note() - midi_region()->start_beats());
+}