Remove cruft
[ardour.git] / gtk2_ardour / midi_region_view.cc
index c0d8a3c59968892b545cbc6b98a8fda0198cc5ec..c83e1b87ad3bbdc148f09be11521473f572c36e2 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"
 
@@ -113,7 +113,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _channel_selection_scoped_note (0)
        , _mouse_state(None)
        , _pressed_button(0)
-       , _sort_needed (true)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -126,6 +125,10 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _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));
 
@@ -156,7 +159,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _channel_selection_scoped_note (0)
        , _mouse_state(None)
        , _pressed_button(0)
-       , _sort_needed (true)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -169,6 +171,10 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _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));
@@ -183,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,7 +212,6 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
        , _channel_selection_scoped_note (0)
        , _mouse_state(None)
        , _pressed_button(0)
-       , _sort_needed (true)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -236,7 +243,6 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<M
        , _channel_selection_scoped_note (0)
        , _mouse_state(None)
        , _pressed_button(0)
-       , _sort_needed (true)
        , _optimization_iterator (_events.end())
        , _list_editor (0)
        , _no_sound_notes (false)
@@ -303,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 ();
 }
 
@@ -340,6 +347,9 @@ MidiRegionView::canvas_group_event(GdkEvent* ev)
                return RegionView::canvas_group_event (ev);
        }
 
+       //For now, move the snapped cursor aside so it doesn't bother you during internal editing
+       //trackview.editor().set_snapped_cursor_position(_region->position());
+
        bool r;
 
        switch (ev->type) {
@@ -409,8 +419,8 @@ MidiRegionView::leave_notify (GdkEventCrossing*)
 void
 MidiRegionView::mouse_mode_changed ()
 {
-       // Adjust frame colour (become more transparent for internal tools)
-       set_frame_color();
+       // Adjust sample colour (become more transparent for internal tools)
+       set_sample_color();
 
        if (_entered) {
                if (!trackview.editor().internal_editing()) {
@@ -450,12 +460,12 @@ MidiRegionView::enter_internal (uint32_t state)
                _grabbed_keyboard = true;
        }
 
-       // Lower frame handles below notes so they don't steal events
-       if (frame_handle_start) {
-               frame_handle_start->lower_to_bottom();
+       // Lower sample handles below notes so they don't steal events
+       if (sample_handle_start) {
+               sample_handle_start->lower_to_bottom();
        }
-       if (frame_handle_end) {
-               frame_handle_end->lower_to_bottom();
+       if (sample_handle_end) {
+               sample_handle_end->lower_to_bottom();
        }
 }
 
@@ -471,12 +481,12 @@ MidiRegionView::leave_internal()
                _grabbed_keyboard = false;
        }
 
-       // Raise frame handles above notes so they catch events
-       if (frame_handle_start) {
-               frame_handle_start->raise_to_top();
+       // Raise sample handles above notes so they catch events
+       if (sample_handle_start) {
+               sample_handle_start->raise_to_top();
        }
-       if (frame_handle_end) {
-               frame_handle_end->raise_to_top();
+       if (sample_handle_end) {
+               sample_handle_end->raise_to_top();
        }
 }
 
@@ -673,10 +683,8 @@ MidiRegionView::motion (GdkEventMotion* ev)
 
        }
 
-       /* we may be dragging some non-note object (eg. patch-change, sysex)
-        */
-
-       return editor.drags()->motion_handler ((GdkEvent *) ev, false);
+       //let RegionView do it's thing.  drags are handled in here
+       return RegionView::canvas_group_event ((GdkEvent *) ev);
 }
 
 
@@ -741,7 +749,7 @@ MidiRegionView::key_press (GdkEventKey* ev)
                bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
                bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
 
-               change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
+               change_note_lengths (fine, shorter, Temporal::Beats(), start, end);
 
                return true;
 
@@ -910,13 +918,13 @@ MidiRegionView::show_list_editor ()
 }
 
 /** Add a note to the model, and the view, at a canvas (click) coordinate.
- * \param t time in frames relative to the position of the region
+ * \param t time in samples relative to the position of the region
  * \param y vertical position in pixels
  * \param length duration of the note in beats
  * \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, uint32_t state, bool shift_snap)
+MidiRegionView::create_note_at (samplepos_t t, double y, Temporal::Beats length, uint32_t state, bool shift_snap)
 {
        if (length < 2 * DBL_EPSILON) {
                return;
@@ -930,9 +938,9 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, ui
                return;
        }
 
-       // Start of note in frames relative to region start
+       // Start of note in samples relative to region start
        const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
-       Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
+       Temporal::Beats beat_time = snap_sample_to_grid_underneath (t, divisions, shift_snap);
 
        const double  note     = view->y_to_note(y);
        const uint8_t chan     = mtv->get_channel_for_add();
@@ -949,7 +957,6 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, ui
 
        start_note_diff_command(_("add note"));
 
-       clear_editor_note_selection ();
        note_diff_add_note (new_note, true, false);
 
        apply_diff();
@@ -970,10 +977,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();
@@ -988,7 +993,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();
@@ -1039,7 +1044,7 @@ MidiRegionView::note_diff_add_change (NoteBase* ev,
 void
 MidiRegionView::note_diff_add_change (NoteBase* ev,
                                       MidiModel::NoteDiffCommand::Property property,
-                                      Evoral::Beats val)
+                                      Temporal::Beats val)
 {
        if (_note_diff_command) {
                _note_diff_command->change (ev->note(), property, val);
@@ -1047,7 +1052,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;
@@ -1056,15 +1061,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);
@@ -1090,24 +1094,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;
@@ -1120,16 +1125,40 @@ MidiRegionView::find_canvas_note (Evoral::event_id_t id)
        Events::iterator it;
 
        for (it = _events.begin(); it != _events.end(); ++it) {
-               if ((*it)->note()->id() == id) {
-                       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)
+MidiRegionView::get_events (Events& e, Evoral::Sequence<Temporal::Beats>::NoteOperator op, uint8_t val, int chan_mask)
 {
        MidiModel::Notes notes;
        _model->get_notes (notes, op, val, chan_mask);
@@ -1137,7 +1166,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));
                }
        }
 }
@@ -1147,14 +1176,14 @@ MidiRegionView::redisplay_model()
 {
        if (_active_notes) {
                // Currently recording
-               const framecnt_t zoom = trackview.editor().get_current_zoom();
+               const samplecnt_t zoom = trackview.editor().get_current_zoom();
                if (zoom != _last_display_zoom) {
                        /* Update resolved canvas notes to reflect changes in zoom without
                           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;
@@ -1166,80 +1195,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<Evoral::event_id_t>::iterator it;
-                       for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
-                               if ((*it) == note->id()) {
-                                       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 ();
@@ -1248,12 +1304,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
@@ -1274,13 +1324,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 samplecnt_t region_samples = source_beats_to_region_samples ((*i)->time());
+
+                       if (region_samples < 0 || region_samples >= _region->length()) {
+                               p->hide();
+                       } else {
+                               const double x = trackview.editor().sample_to_pixel (region_samples);
+                               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);
+               }
        }
 }
 
@@ -1293,26 +1361,21 @@ 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;
                        }
                }
 
                if (have_periodic_system_messages) {
-                       double zoom = trackview.editor().get_current_zoom (); // frames per pixel
+                       double zoom = trackview.editor().get_current_zoom (); // samples per pixel
 
                        /* get an approximate value for the number of samples per video frame */
 
-                       double video_frame = trackview.session()->frame_rate() * (1.0/30);
+                       double video_frame = trackview.session()->sample_rate() * (1.0/30);
 
                        /* if we are zoomed out beyond than the cutoff (i.e. more
-                        * frames per pixel than frames per 4 video frames), don't
+                        * samples per pixel than samples per 4 video frames), don't
                         * show periodic sysex messages.
                         */
 
@@ -1325,17 +1388,12 @@ MidiRegionView::display_sysexes()
        }
 
        for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
+               MidiModel::SysExPtr sysex_ptr = *i;
+               Temporal::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;
                        }
                }
 
@@ -1349,24 +1407,31 @@ MidiRegionView::display_sysexes()
                }
                string text = str.str();
 
-               const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
+               const double x = trackview.editor().sample_to_pixel(source_beats_to_region_samples(time));
 
                double height = midi_stream_view()->contents_height();
 
                // 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));
-
-               // Show unless message is beyond the region bounds
-               if (time - _region->start() >= _region->length() || time < _region->start()) {
-                       sysex->hide();
+               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->show();
+                       sysex->set_height (height);
+                       sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
                }
 
-               _sys_exes.push_back(sysex);
+               // Show unless message is beyond the region bounds
+// XXX REQUIRES APPROPRIATE OPERATORS FOR Temporal::Beats and samplepos? say what?
+#warning paul fix this
+//             if (time - _region->start() >= _region->length() || time < _region->start()) {
+//                     sysex->hide();
+//             } else {
+//                     sysex->show();
+//             }
        }
 }
 
@@ -1384,7 +1449,6 @@ MidiRegionView::~MidiRegionView ()
                end_write();
        }
        _entered_note = 0;
-       _selection.clear();
        clear_events ();
 
        delete _note_group;
@@ -1427,11 +1491,15 @@ MidiRegionView::reset_width_dependent_items (double pixel_width)
                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();
                }
        }
 
@@ -1454,7 +1522,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) {
@@ -1480,44 +1548,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*
@@ -1536,15 +1567,16 @@ MidiRegionView::add_ghost (TimeAxisView& tv)
                ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
        }
 
-       for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
-               ghost->add_note(*i);
-       }
-
        ghost->set_colors ();
        ghost->set_height ();
        ghost->set_duration (_region->length() / samples_per_pixel);
-       ghosts.push_back (ghost);
 
+       for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+               ghost->add_note(i->second);
+       }
+
+       ghosts.push_back (ghost);
+       enable_display (true);
        return ghost;
 }
 
@@ -1579,7 +1611,7 @@ MidiRegionView::end_write()
 /** Resolve an active MIDI note (while recording).
  */
 void
-MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
+MidiRegionView::resolve_note(uint8_t note, Temporal::Beats end_time)
 {
        if (midi_view()->note_mode() != Sustained) {
                return;
@@ -1592,9 +1624,9 @@ MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
                        end_time - _active_notes[note]->note()->time());
 
                /* End time is relative to the region being recorded. */
-               const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
+               const samplepos_t end_time_samples = region_beats_to_region_samples(end_time);
 
-               _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
+               _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_samples));
                _active_notes[note]->set_outline_all ();
                _active_notes[note] = 0;
        }
@@ -1675,10 +1707,10 @@ MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bo
 
        /* 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());
+                             note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
 
-       visible = (note->note() >= midi_stream_view()->lowest_note()) &&
-               (note->note() <= midi_stream_view()->highest_note());
+       visible = (note->note() >= _current_range_min) &&
+               (note->note() <= _current_range_max);
 
        return !outside;
 }
@@ -1707,11 +1739,11 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
        boost::shared_ptr<NoteType> note = ev->note();
 
        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 samplepos_t note_start_samples = map.sample_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
 
-       const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
+       const double x0 = trackview.editor().sample_to_pixel (note_start_samples);
        double x1;
-       const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
+       const double y0 = 1 + floor(note_to_y(note->note()));
        double y1;
 
        /* trim note display to not overlap the end of its region */
@@ -1722,14 +1754,14 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
                        note_end_time = mr->start_beats() + mr->length_beats();
                }
 
-               const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
+               const samplepos_t note_end_samples = map.sample_at_quarter_note (session_source_start + note_end_time) - _region->position();
 
-               x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
+               x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_samples)) - 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);
 
        ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
 
@@ -1759,14 +1791,6 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
        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);
-                       }
-               }
-       }
 }
 
 void
@@ -1775,11 +1799,11 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
        boost::shared_ptr<NoteType> note = ev->note();
 
        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 samplepos_t note_start_samples = trackview.session()->tempo_map().sample_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 x = trackview.editor().sample_to_pixel(note_start_samples);
+       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) {
@@ -1796,14 +1820,6 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
        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_hit (ev);
-                       }
-               }
-       }
 }
 
 /** Add a MIDI note to the view (with length).
@@ -1819,7 +1835,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);
 
@@ -1827,9 +1843,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);
 
@@ -1857,7 +1873,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();
@@ -1875,18 +1891,18 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
 
 void
 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
-                               Evoral::Beats pos, Evoral::Beats len)
+                               Temporal::Beats pos, Temporal::Beats len)
 {
        boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
 
        /* potentially extend region to hold new note */
 
-       framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
-       framepos_t region_end = _region->last_frame();
+       samplepos_t end_sample = source_beats_to_absolute_samples (new_note->end_time());
+       samplepos_t region_end = _region->last_sample();
 
-       if (end_frame > region_end) {
+       if (end_sample > region_end) {
                /* XX sets length in beats from audio space. make musical */
-               _region->set_length (end_frame - _region->position(), 0);
+               _region->set_length (end_sample - _region->position(), 0);
        }
 
        MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
@@ -1907,7 +1923,7 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity
 }
 
 void
-MidiRegionView::step_sustain (Evoral::Beats beats)
+MidiRegionView::step_sustain (Temporal::Beats beats)
 {
        change_note_lengths (false, false, beats, false, true);
 }
@@ -1920,8 +1936,8 @@ MidiRegionView::step_sustain (Evoral::Beats beats)
 void
 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
 {
-       framecnt_t region_frames = source_beats_to_region_frames (patch->time());
-       const double x = trackview.editor().sample_to_pixel (region_frames);
+       samplecnt_t region_samples = source_beats_to_region_samples (patch->time());
+       const double x = trackview.editor().sample_to_pixel (region_samples);
 
        double const height = midi_stream_view()->contents_height();
 
@@ -1929,18 +1945,20 @@ 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
-               if (region_frames < 0 || region_frames >= _region->length()) {
+               if (region_samples < 0 || region_samples >= _region->length()) {
                        patch_change->hide();
                } else {
                        patch_change->show();
@@ -1949,7 +1967,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
@@ -1960,13 +1990,13 @@ MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
 
 /// Return true iff @p pc applies to the given time on the given channel.
 static bool
-patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
+patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats time, uint8_t channel)
 {
        return pc->time() <= time && pc->channel() == channel;
 }
 
 void
-MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
+MidiRegionView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
 {
        // The earliest event not before time
        MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
@@ -1992,6 +2022,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()) {
@@ -2006,12 +2037,12 @@ 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 ();
 }
 
 void
-MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
+MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Temporal::Beats> & new_change)
 {
        string name = _("alter patch change");
        trackview.editor().begin_reversible_command (name);
@@ -2036,27 +2067,32 @@ 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 ();
 }
 
 /** Add a patch change to the region.
- *  @param t Time in frames relative to region position
+ *  @param t Time in samples relative to region position
  *  @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
  *  MidiTimeAxisView::get_channel_for_add())
  */
 void
-MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
+MidiRegionView::add_patch_change (samplecnt_t t, Evoral::PatchChange<Temporal::Beats> const & patch)
 {
-       MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
        string name = _("add patch change");
 
        trackview.editor().begin_reversible_command (name);
        MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
        c->add (MidiModel::PatchChangePtr (
-                       new Evoral::PatchChange<Evoral::Beats> (
-                               absolute_frames_to_source_beats (_region->position() + t),
-                               mtv->get_channel_for_add(), patch.program(), patch.bank()
+                       new Evoral::PatchChange<Temporal::Beats> (
+                               absolute_samples_to_source_beats (_region->position() + t),
+                               patch.channel(), patch.program(), patch.bank()
                                )
                        )
                );
@@ -2064,12 +2100,11 @@ 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 ();
 }
 
 void
-MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
+MidiRegionView::move_patch_change (PatchChange& pc, Temporal::Beats t)
 {
        trackview.editor().begin_reversible_command (_("move patch change"));
        MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
@@ -2077,7 +2112,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 ();
 }
 
@@ -2085,12 +2119,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 ();
 }
 
@@ -2203,19 +2238,19 @@ 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);
        }
 }
 
 void
-MidiRegionView::select_range (framepos_t start, framepos_t end)
+MidiRegionView::select_range (samplepos_t start, samplepos_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());
+               samplepos_t t = source_beats_to_absolute_samples(i->first->time());
                if (t >= start && t <= end) {
-                       add_to_selection (*i);
+                       add_to_selection (i->second);
                }
        }
 }
@@ -2224,10 +2259,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);
                }
        }
 }
@@ -2363,8 +2398,8 @@ MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
        } else {
                /* find end of latest note selected, select all between that and the start of "ev" */
 
-               Evoral::Beats earliest = Evoral::MaxBeats;
-               Evoral::Beats latest   = Evoral::Beats();
+               Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
+               Temporal::Beats latest   = Temporal::Beats();
 
                for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                        if ((*i)->note()->end_time() > latest) {
@@ -2387,11 +2422,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);
                        }
-
                }
        }
 }
@@ -2403,15 +2437,15 @@ MidiRegionView::note_deselected(NoteBase* ev)
 }
 
 void
-MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
+MidiRegionView::update_drag_selection(samplepos_t start, samplepos_t end, double gy0, double gy1, bool extend)
 {
        PublicEditor& editor = trackview.editor();
 
        // Convert to local coordinates
-       const framepos_t p  = _region->position();
+       const samplepos_t p  = _region->position();
        const double     y  = midi_view()->y_position();
-       const double     x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
-       const double     x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
+       const double     x0 = editor.sample_to_pixel(max((samplepos_t)0, start - p));
+       const double     x1 = editor.sample_to_pixel(max((samplepos_t)0, end - p));
        const double     y0 = max(0.0, gy0 - y);
        const double     y1 = max(0.0, gy1 - y);
 
@@ -2420,14 +2454,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);
                }
        }
 
@@ -2462,13 +2496,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);
                }
        }
 }
@@ -2517,12 +2551,10 @@ MidiRegionView::add_to_selection (NoteBase* ev)
        }
 }
 
-void
-MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
+Temporal::Beats
+MidiRegionView::earliest_in_selection ()
 {
-       typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
-       PossibleChord to_play;
-       Evoral::Beats earliest = Evoral::MaxBeats;
+       Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
 
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                if ((*i)->note()->time() < earliest) {
@@ -2530,11 +2562,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;
+       Temporal::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.sample_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.sample_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.sample_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()) {
@@ -2560,81 +2628,223 @@ 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;
+       Temporal::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.sample_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.sample_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.sample_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;
-       }
-       TempoMap& map (trackview.session()->tempo_map());
+               /*
+                 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"));
 
-               double const start_qn = _region->quarter_note() - midi_region()->start_beats();
-               framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
-               Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
-               if (new_time < 0) {
-                       continue;
+               for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
+
+                       Temporal::Beats new_time = Temporal::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);
                }
 
-               note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
+               // 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;
+               }
+
+               start_note_diff_command (_("copy notes"));
+
+               for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
+
+                       /* update time */
+                       Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
+
+                       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);
 
-               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);
 
-               // 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);
 
-               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);
 
-               note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
+                       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);
        }
 }
 
 /** @param x Pixel relative to the region position.
  *  @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
  *  Used for inverting the snap logic with key modifiers and snap delta calculation.
- *  @return Snapped frame relative to the region position.
+ *  @return Snapped sample relative to the region position.
  */
-framepos_t
+samplepos_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_sample_to_sample (editor.pixel_to_sample (x), ensure_snap).sample;
 }
 
 /** @param x Pixel relative to the region position.
@@ -2650,55 +2860,55 @@ MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
 double
 MidiRegionView::get_position_pixels()
 {
-       framepos_t region_frame = get_position();
-       return trackview.editor().sample_to_pixel(region_frame);
+       samplepos_t region_sample = get_position();
+       return trackview.editor().sample_to_pixel(region_sample);
 }
 
 double
 MidiRegionView::get_end_position_pixels()
 {
-       framepos_t frame = get_position() + get_duration ();
-       return trackview.editor().sample_to_pixel(frame);
+       samplepos_t sample = get_position() + get_duration ();
+       return trackview.editor().sample_to_pixel(sample);
 }
 
-framepos_t
-MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
+samplepos_t
+MidiRegionView::source_beats_to_absolute_samples(Temporal::Beats beats) const
 {
-       /* the time converter will return the frame corresponding to `beats'
+       /* the time converter will return the sample corresponding to `beats'
           relative to the start of the source. The start of the source
           is an implied position given by region->position - region->start
        */
-       const framepos_t source_start = _region->position() - _region->start();
+       const samplepos_t source_start = _region->position() - _region->start();
        return  source_start +  _source_relative_time_converter.to (beats);
 }
 
-Evoral::Beats
-MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
+Temporal::Beats
+MidiRegionView::absolute_samples_to_source_beats(samplepos_t samples) const
 {
-       /* the `frames' argument needs to be converted into a frame count
+       /* the `samples' argument needs to be converted into a sample count
           relative to the start of the source before being passed in to the
           converter.
        */
-       const framepos_t source_start = _region->position() - _region->start();
-       return  _source_relative_time_converter.from (frames - source_start);
+       const samplepos_t source_start = _region->position() - _region->start();
+       return  _source_relative_time_converter.from (samples - source_start);
 }
 
-framepos_t
-MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
+samplepos_t
+MidiRegionView::region_beats_to_region_samples(Temporal::Beats beats) const
 {
        return _region_relative_time_converter.to(beats);
 }
 
-Evoral::Beats
-MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
+Temporal::Beats
+MidiRegionView::region_samples_to_region_beats(samplepos_t samples) const
 {
-       return _region_relative_time_converter.from(frames);
+       return _region_relative_time_converter.from(samples);
 }
 
 double
-MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
+MidiRegionView::region_samples_to_region_beats_double (samplepos_t samples) const
 {
-       return _region_relative_time_converter_double.from(frames);
+       return _region_relative_time_converter_double.from(samples);
 }
 
 void
@@ -2779,7 +2989,7 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_
                }
 
                if (current_x < 0) {
-                       // This works even with snapping because RegionView::snap_frame_to_frame()
+                       // This works even with snapping because RegionView::snap_sample_to_sample()
                        // snaps forward if the snapped sample is before the beginning of the region
                        current_x = 0;
                }
@@ -2805,15 +3015,15 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_
 
                if (!cursor_set) {
                        /* Convert snap delta from pixels to beats. */
-                       framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
+                       samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
                        double snap_delta_beats = 0.0;
                        int sign = 1;
 
                        /* negative beat offsets aren't allowed */
                        if (snap_delta_samps > 0) {
-                               snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
+                               snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
                        } else if (snap_delta_samps < 0) {
-                               snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
+                               snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
                                sign = -1;
                        }
 
@@ -2826,10 +3036,11 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_
                        } else {
                                snapped_x = trackview.editor ().pixel_to_sample (current_x);
                        }
-                       const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
+
+                       const Temporal::Beats beats = Temporal::Beats (tmap.exact_beat_at_sample (snapped_x + midi_region()->position(), divisions)
                                                                     - midi_region()->beat()) + midi_region()->start_beats();
 
-                       Evoral::Beats len         = Evoral::Beats();
+                       Temporal::Beats len         = Temporal::Beats();
 
                        if (at_front) {
                                if (beats < canvas_note->note()->end_time()) {
@@ -2842,13 +3053,15 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_
                                }
                        }
 
-                       len = std::max(Evoral::Beats(1 / 512.0), len);
+                       len = std::max(Temporal::Beats(1 / 512.0), len);
 
                        char buf[16];
                        snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
                        show_verbose_cursor (buf, 0, 0);
 
                        cursor_set = true;
+
+                       trackview.editor().set_snapped_cursor_position ( snapped_x + midi_region()->position() );
                }
 
        }
@@ -2899,20 +3112,20 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_
                }
 
                /* Convert snap delta from pixels to beats with sign. */
-               framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
+               samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
                double snap_delta_beats = 0.0;
                int sign = 1;
 
                if (snap_delta_samps > 0) {
-                       snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
+                       snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
                } else if (snap_delta_samps < 0) {
-                       snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
+                       snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
                        sign = -1;
                }
 
                uint32_t divisions = 0;
-               /* Convert the new x position to a frame within the source */
-               framepos_t current_fr;
+               /* Convert the new x position to a sample within the source */
+               samplepos_t current_fr;
                if (with_snap) {
                        current_fr = snap_pixel_to_sample (current_x, ensure_snap);
                        divisions = trackview.editor().get_grid_music_divisions (0);
@@ -2921,13 +3134,13 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_
                }
 
                /* and then to beats */
-               const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
+               const double e_qaf = tmap.exact_qn_at_sample (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);
+               const Temporal::Beats x_beats = Temporal::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));
-                       Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
+                       Temporal::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
                        len += canvas_note->note()->length();
 
                        if (!!len) {
@@ -2936,7 +3149,7 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_
                }
 
                if (!at_front) {
-                       Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
+                       Temporal::Beats len = std::max(Temporal::Beats(1 / 512.0),
                                                     x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
                        note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
                }
@@ -2993,12 +3206,12 @@ MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
 }
 
 void
-MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
+MidiRegionView::trim_note (NoteBase* event, Temporal::Beats front_delta, Temporal::Beats end_delta)
 {
        bool change_start = false;
        bool change_length = false;
-       Evoral::Beats new_start;
-       Evoral::Beats new_length;
+       Temporal::Beats new_start;
+       Temporal::Beats new_length;
 
        /* NOTE: the semantics of the two delta arguments are slightly subtle:
 
@@ -3013,7 +3226,7 @@ MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::B
                if (front_delta < 0) {
 
                        if (event->note()->time() < -front_delta) {
-                               new_start = Evoral::Beats();
+                               new_start = Temporal::Beats();
                        } else {
                                new_start = event->note()->time() + front_delta; // moves earlier
                        }
@@ -3028,7 +3241,7 @@ MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::B
 
                } else {
 
-                       Evoral::Beats new_pos = event->note()->time() + front_delta;
+                       Temporal::Beats new_pos = event->note()->time() + front_delta;
 
                        if (new_pos < event->note()->end_time()) {
                                new_start = event->note()->time() + front_delta;
@@ -3087,14 +3300,14 @@ MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
 }
 
 void
-MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
+MidiRegionView::change_note_time (NoteBase* event, Temporal::Beats delta, bool relative)
 {
-       Evoral::Beats new_time;
+       Temporal::Beats new_time;
 
        if (relative) {
                if (delta < 0.0) {
                        if (event->note()->time() < -delta) {
-                               new_time = Evoral::Beats();
+                               new_time = Temporal::Beats();
                        } else {
                                new_time = event->note()->time() + delta;
                        }
@@ -3109,7 +3322,7 @@ MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool rel
 }
 
 void
-MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
+MidiRegionView::change_note_length (NoteBase* event, Temporal::Beats t)
 {
        note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
 }
@@ -3221,11 +3434,11 @@ MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
 }
 
 void
-MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
+MidiRegionView::change_note_lengths (bool fine, bool shorter, Temporal::Beats delta, bool start, bool end)
 {
        if (!delta) {
                if (fine) {
-                       delta = Evoral::Beats(1.0/128.0);
+                       delta = Temporal::Beats(1.0/128.0);
                } else {
                        /* grab the current grid distance */
                        delta = get_grid_beats(_region->position());
@@ -3245,8 +3458,8 @@ MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delt
                /* note the negation of the delta for start */
 
                trim_note (*i,
-                          (start ? -delta : Evoral::Beats()),
-                          (end   ? delta  : Evoral::Beats()));
+                          (start ? -delta : Temporal::Beats()),
+                          (end   ? delta  : Temporal::Beats()));
                i = next;
        }
 
@@ -3266,42 +3479,41 @@ MidiRegionView::nudge_notes (bool forward, bool fine)
           into a vector and sort before using the first one.
        */
 
-       const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
-       Evoral::Beats    delta;
+       const samplepos_t ref_point = source_beats_to_absolute_samples ((*(_selection.begin()))->note()->time());
+       Temporal::Beats  delta;
 
        if (!fine) {
 
                /* non-fine, move by 1 bar regardless of snap */
-               delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
+               delta = Temporal::Beats(trackview.session()->tempo_map().meter_at_sample (ref_point).divisions_per_bar());
 
        } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
 
                /* grid is off - use nudge distance */
 
-               framepos_t       unused;
-               const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
-               delta = region_frames_to_region_beats (fabs ((double)distance));
+               samplepos_t       unused;
+               const samplecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
+               delta = region_samples_to_region_beats (fabs ((double)distance));
 
        } else {
 
                /* use grid */
 
-               framepos_t next_pos = ref_point;
-
+               MusicSample next_pos (ref_point, 0);
                if (forward) {
-                       if (max_framepos - 1 < next_pos) {
-                               next_pos += 1;
+                       if (max_samplepos - 1 < next_pos.sample) {
+                               next_pos.sample += 1;
                        }
                } else {
-                       if (next_pos == 0) {
+                       if (next_pos.sample == 0) {
                                return;
                        }
-                       next_pos -= 1;
+                       next_pos.sample -= 1;
                }
 
-               trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
-               const framecnt_t distance = ref_point - next_pos;
-               delta = region_frames_to_region_beats (fabs ((double)distance));
+               trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), SnapToGrid, false);
+               const samplecnt_t distance = ref_point - next_pos.sample;
+               delta = region_samples_to_region_beats (fabs ((double)distance));
        }
 
        if (!delta) {
@@ -3458,7 +3670,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 ();
@@ -3530,7 +3742,7 @@ MidiRegionView::selection_as_cut_buffer () const
 
 /** This method handles undo */
 bool
-MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
+MidiRegionView::paste (samplepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
 {
        bool commit = false;
        // Paste notes, if available
@@ -3563,7 +3775,7 @@ MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContex
 
 /** This method handles undo */
 void
-MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
+MidiRegionView::paste_internal (samplepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
 {
        if (mcb.empty()) {
                return;
@@ -3571,14 +3783,14 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time
 
        start_note_diff_command (_("paste"));
 
-       const Evoral::Beats snap_beats    = get_grid_beats(pos);
-       const Evoral::Beats first_time    = (*mcb.notes().begin())->time();
-       const Evoral::Beats last_time     = (*mcb.notes().rbegin())->end_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 quarter_note     = absolute_frames_to_source_beats(pos) + paste_offset;
-       Evoral::Beats       end_point     = Evoral::Beats();
+       const Temporal::Beats snap_beats    = get_grid_beats(pos);
+       const Temporal::Beats first_time    = (*mcb.notes().begin())->time();
+       const Temporal::Beats last_time     = (*mcb.notes().rbegin())->end_time();
+       const Temporal::Beats duration      = last_time - first_time;
+       const Temporal::Beats snap_duration = duration.snap_to(snap_beats);
+       const Temporal::Beats paste_offset  = snap_duration * paste_count;
+       const Temporal::Beats quarter_note     = absolute_samples_to_source_beats(pos) + paste_offset;
+       Temporal::Beats     end_point     = Temporal::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,
@@ -3605,16 +3817,16 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time
 
        /* if we pasted past the current end of the region, extend the region */
 
-       framepos_t end_frame = source_beats_to_absolute_frames (end_point);
-       framepos_t region_end = _region->position() + _region->length() - 1;
+       samplepos_t end_sample = source_beats_to_absolute_samples (end_point);
+       samplepos_t region_end = _region->position() + _region->length() - 1;
 
-       if (end_frame > region_end) {
+       if (end_sample > region_end) {
 
-               DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
+               DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_sample));
 
                _region->clear_changes ();
                /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
-               _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
+               _region->set_length (end_sample - _region->position(), trackview.editor().get_grid_music_divisions (0));
                trackview.session()->add_command (new StatefulDiffCommand (_region));
        }
 
@@ -3627,53 +3839,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);
        }
 }
 
@@ -3682,35 +3888,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);
        }
 }
 
@@ -3719,18 +3933,18 @@ 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);
                }
        }
 }
@@ -3749,11 +3963,18 @@ MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
 
        PublicEditor& editor = trackview.editor ();
 
-       framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
+       samplepos_t const unsnapped_sample = editor.pixel_to_sample (x);
 
        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);
+       const Temporal::Beats snapped_beats = snap_sample_to_grid_underneath (unsnapped_sample, 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) {
@@ -3765,11 +3986,11 @@ MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
        }
 
        /* calculate time in beats relative to start of source */
-       const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
+       const Temporal::Beats length = get_grid_beats(unsnapped_sample + _region->position());
 
        _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 (snapped_beats));
        /* the ghost note does not appear in ghost regions, so pass false in here */
@@ -3835,16 +4056,16 @@ 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);
 
        uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
 
        if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
-               get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
+               get_events (e, Evoral::Sequence<Temporal::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
        } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
-               get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
+               get_events (e, Evoral::Sequence<Temporal::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
        } else {
                return;
        }
@@ -3856,8 +4077,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);
                }
        }
 
@@ -3872,8 +4093,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 */
@@ -3886,7 +4110,7 @@ MidiRegionView::enable_display (bool yn)
 }
 
 void
-MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
+MidiRegionView::show_step_edit_cursor (Temporal::Beats pos)
 {
        if (_step_edit_cursor == 0) {
                ArdourCanvas::Item* const group = get_canvas_group();
@@ -3903,12 +4127,12 @@ MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
 }
 
 void
-MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
+MidiRegionView::move_step_edit_cursor (Temporal::Beats pos)
 {
        _step_edit_cursor_position = pos;
 
        if (_step_edit_cursor) {
-               double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
+               double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_samples (pos));
                _step_edit_cursor->set_x0 (pixel);
                set_step_edit_cursor_width (_step_edit_cursor_width);
        }
@@ -3923,14 +4147,14 @@ MidiRegionView::hide_step_edit_cursor ()
 }
 
 void
-MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
+MidiRegionView::set_step_edit_cursor_width (Temporal::Beats beats)
 {
        _step_edit_cursor_width = beats;
 
        if (_step_edit_cursor) {
                _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
-                                                  region_beats_to_region_frames (_step_edit_cursor_position + beats)
-                                                  - region_beats_to_region_frames (_step_edit_cursor_position)));
+                                                  region_beats_to_region_samples (_step_edit_cursor_position + beats)
+                                                  - region_beats_to_region_samples (_step_edit_cursor_position)));
        }
 }
 
@@ -3955,10 +4179,10 @@ MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
 
        boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
 
-       framepos_t back = max_framepos;
+       samplepos_t back = max_samplepos;
 
        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) {
@@ -3968,13 +4192,13 @@ MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
                        }
                }
 
-               /* convert from session frames to source beats */
-               Evoral::Beats const time_beats = _source_relative_time_converter.from(
+               /* convert from session samples to source beats */
+               Temporal::Beats const time_beats = _source_relative_time_converter.from(
                        ev.time() - src->timeline_position() + _region->start());
 
                if (ev.type() == MIDI_CMD_NOTE_ON) {
                        boost::shared_ptr<NoteType> note (
-                               new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
+                               new NoteType (ev.channel(), time_beats, Temporal::Beats(), ev.note(), ev.velocity()));
 
                        add_note (note, true);
 
@@ -4128,23 +4352,23 @@ MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
        return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
 }
 
-/** @param p A session framepos.
+/** @param p A session samplepos.
  *  @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.
  */
-Evoral::Beats
-MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
+Temporal::Beats
+MidiRegionView::snap_sample_to_grid_underneath (samplepos_t p, int32_t divisions, bool shift_snap) const
 {
        TempoMap& map (trackview.session()->tempo_map());
-       double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
+       double eqaf = map.exact_qn_at_sample (p + _region->position(), divisions);
 
        if (divisions != 0 && shift_snap) {
-               const double qaf = map.quarter_note_at_frame (p + _region->position());
+               const double qaf = map.quarter_note_at_sample (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 Temporal::Beats grid_beats = get_grid_beats (p + _region->position());
                const double rem = eqaf - qaf;
                if (rem >= 0.0) {
                        eqaf -= grid_beats.to_double();
@@ -4152,7 +4376,7 @@ MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions,
        }
        const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
 
-       return Evoral::Beats (eqaf - session_start_off);
+       return Temporal::Beats (eqaf - session_start_off);
 }
 
 ChannelMode
@@ -4170,14 +4394,41 @@ MidiRegionView::get_selected_channels () const
 }
 
 
-Evoral::Beats
-MidiRegionView::get_grid_beats(framepos_t pos) const
+Temporal::Beats
+MidiRegionView::get_grid_beats(samplepos_t pos) const
 {
        PublicEditor& editor  = trackview.editor();
        bool          success = false;
-       Evoral::Beats beats   = editor.get_grid_type_as_beats (success, pos);
+       Temporal::Beats beats   = editor.get_grid_type_as_beats (success, pos);
        if (!success) {
-               beats = Evoral::Beats(1);
+               beats = Temporal::Beats(1);
        }
        return beats;
 }
+uint8_t
+MidiRegionView::y_to_note (double y) const
+{
+       int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
+               + _current_range_min;
+
+       if (n < 0) {
+               return 0;
+       } else if (n > 127) {
+               return 127;
+       }
+
+       /* min due to rounding and/or off-by-one errors */
+       return min ((uint8_t) n, _current_range_max);
+}
+
+double
+MidiRegionView::note_to_y(uint8_t note) const
+{
+       return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
+}
+
+double
+MidiRegionView::session_relative_qn (double qn) const
+{
+       return qn + (region()->quarter_note() - midi_region()->start_beats());
+}