Another similar fix for use of an uninitialized variable.
[ardour.git] / gtk2_ardour / midi_region_view.cc
index 5339131c8ec2ee10dc4852107ac89a46f5469cee..aa964749ff8ad4d6507bb04132ef7105617ea2fc 100644 (file)
@@ -20,6 +20,7 @@
 #include <cmath>
 #include <cassert>
 #include <algorithm>
+#include <ostream>
 
 #include <gtkmm.h>
 
 
 #include <sigc++/signal.h>
 
-#include <ardour/playlist.h>
-#include <ardour/tempo.h>
-#include <ardour/midi_region.h>
-#include <ardour/midi_source.h>
-#include <ardour/midi_diskstream.h>
-#include <ardour/midi_model.h>
-#include <ardour/midi_patch_manager.h>
+#include "ardour/playlist.h"
+#include "ardour/tempo.h"
+#include "ardour/midi_region.h"
+#include "ardour/midi_source.h"
+#include "ardour/midi_diskstream.h"
+#include "ardour/midi_model.h"
+#include "ardour/midi_patch_manager.h"
 
-#include <evoral/Parameter.hpp>
-#include <evoral/Control.hpp>
+#include "evoral/Parameter.hpp"
+#include "evoral/Control.hpp"
 
 #include "streamview.h"
 #include "midi_region_view.h"
@@ -65,7 +66,7 @@ using namespace Editing;
 using namespace ArdourCanvas;
 
 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
-               boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color)
+               boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
        : RegionView (parent, tv, r, spu, basic_color)
        , _force_channel(-1)
        , _last_channel_selection(0xFFFF)
@@ -104,7 +105,8 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
 
 
 MidiRegionView::MidiRegionView (const MidiRegionView& other)
-       : RegionView (other)
+       : sigc::trackable(other)
+       , RegionView (other)
        , _force_channel(-1)
        , _last_channel_selection(0xFFFF)
        , _default_note_length(1.0)
@@ -148,7 +150,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<M
 }
 
 void
-MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
+MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
 {
        if (wfd) {
                midi_region()->midi_source(0)->load_model();
@@ -169,7 +171,6 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
        region_locked ();
        
        reset_width_dependent_items (_pixel_width);
-       //reset_width_dependent_items ((double) _region->length() / samples_per_unit);
 
        set_colors ();
 
@@ -194,8 +195,11 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
 bool
 MidiRegionView::canvas_event(GdkEvent* ev)
 {
-       static bool delete_mod = false;
-       static Editing::MidiEditMode original_mode;
+       PublicEditor& editor (trackview.editor());
+
+       if (!editor.internal_editing()) {
+               return false;
+       }
 
        static double drag_start_x, drag_start_y;
        static double last_x, last_y;
@@ -204,21 +208,9 @@ MidiRegionView::canvas_event(GdkEvent* ev)
 
        static ArdourCanvas::SimpleRect* drag_rect = NULL;
 
-       if (trackview.editor().current_mouse_mode() != MouseNote)
-               return false;
-       
-       const Editing::MidiEditMode midi_edit_mode = trackview.editor().current_midi_edit_mode();
-
        switch (ev->type) {
        case GDK_KEY_PRESS:
-               if (ev->key.keyval == GDK_Delete && !delete_mod) {
-                       delete_mod = true;
-                       original_mode = midi_edit_mode;
-                       trackview.editor().set_midi_edit_mode(MidiEditErase);
-                       start_delta_command(_("erase notes"));
-                       _mouse_state = EraseTouchDragging;
-                       return true;
-               } else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) {
+               if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) {
                        _mouse_state = SelectTouchDragging;
                        return true;
                } else if (ev->key.keyval == GDK_Escape) {
@@ -229,15 +221,8 @@ MidiRegionView::canvas_event(GdkEvent* ev)
 
        case GDK_KEY_RELEASE:
                if (ev->key.keyval == GDK_Delete) {
-                       if (_mouse_state == EraseTouchDragging) {
-                               delete_selection();
-                               apply_command();
-                       }
-                       if (delete_mod) {
-                               trackview.editor().set_midi_edit_mode(original_mode);
-                               _mouse_state = None;
-                               delete_mod = false;
-                       }
+                       delete_selection();
+                       apply_command();
                        return true;
                } else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) {
                        _mouse_state = None;
@@ -246,9 +231,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
                return false;
 
        case GDK_BUTTON_PRESS:
-               if (_mouse_state != SelectTouchDragging && 
-                       _mouse_state != EraseTouchDragging &&
-                       ev->button.button == 1) {
+               if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
                        _pressed_button = ev->button.button;
                        _mouse_state = Pressed;
                        return true;
@@ -280,7 +263,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
                case Pressed: // Drag start
 
                        // Select drag start
-                       if (_pressed_button == 1 && midi_edit_mode == MidiEditSelect) {
+                       if (_pressed_button == 1 && editor.current_mouse_mode() == MouseRange) {
                                group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
                                                Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
                                last_x = event_x;
@@ -303,7 +286,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
                                return true;
 
                        // Add note drag start
-                       } else if (midi_edit_mode == MidiEditPencil) {
+                       } else if (editor.current_mouse_mode() == MouseObject) {
                                group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
                                                Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
                                last_x = event_x;
@@ -362,7 +345,6 @@ MidiRegionView::canvas_event(GdkEvent* ev)
                        last_x = event_x;
                        last_y = event_y;
 
-               case EraseTouchDragging:
                case SelectTouchDragging:
                        return false;
 
@@ -384,12 +366,12 @@ MidiRegionView::canvas_event(GdkEvent* ev)
                        
                switch (_mouse_state) {
                case Pressed: // Clicked
-                       switch (midi_edit_mode) {
-                       case MidiEditSelect:
-                       case MidiEditResize:
+                       switch (editor.current_mouse_mode()) {
+                       case MouseRange:
+                       case MouseTimeFX:
                                clear_selection();
                                break;
-                       case MidiEditPencil:
+                       case MouseObject:
                                create_note_at(event_x, event_y, _default_note_length);
                        default: break;
                        }
@@ -438,12 +420,12 @@ MidiRegionView::create_note_at(double x, double y, double length)
        assert(note <= 127.0);
 
        // Start of note in frames relative to region start
-       nframes64_t start_frames = snap_to_frame(trackview.editor().pixel_to_frame(x));
+       nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
        assert(start_frames >= 0);
 
        // Snap length
        length = frames_to_beats(
-                       snap_to_frame(start_frames + beats_to_frames(length)) - start_frames);
+                       snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
 
        const boost::shared_ptr<NoteType> new_note(new NoteType(0,
                        frames_to_beats(start_frames + _region->start()), length,
@@ -475,6 +457,7 @@ MidiRegionView::clear_events()
 
        _events.clear();
        _pgm_changes.clear();
+       _sys_exes.clear();
 }
 
 
@@ -497,7 +480,7 @@ MidiRegionView::start_delta_command(string name)
 }
 
 void
-MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool selected)
+MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
 {
        if (_delta_command) {
                _delta_command->add(note);
@@ -505,6 +488,9 @@ MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool se
        if (selected) {
                _marked_for_selection.insert(note);
        }
+       if (show_velocity) {
+               _marked_for_velocity.insert(note);
+       }
 }
 
 void
@@ -532,6 +518,7 @@ MidiRegionView::apply_command()
        midi_view()->midi_track()->diskstream()->playlist_modified();
 
        _marked_for_selection.clear();
+       _marked_for_velocity.clear();
 }
        
 
@@ -557,23 +544,14 @@ MidiRegionView::redisplay_model()
                _model->read_lock();
                
                MidiModel::Notes notes = _model->notes();
-               /*
-               cerr << _model->midi_source()->name() << " : redisplaying " << notes.size() << endl;
-               for (MidiModel::Notes::iterator i = notes.begin(); i != notes.end(); ++i) {
-                       cerr << "NOTE  time: " << (*i)->time()
-                                << "  pitch: " << int((*i)->note()) 
-                            << "  length: " << (*i)->length() 
-                            << "  end-time: " << (*i)->end_time() 
-                            << "  velocity: " << int((*i)->velocity()) 
-                            << endl;
-               }
-               */
                
                for (size_t i = 0; i < _model->n_notes(); ++i) {
                        add_note(_model->note_at(i));
                }
                
-               display_program_change_flags();
+               display_sysexes();
+
+               display_program_changes();
 
                _model->read_unlock();
 
@@ -583,69 +561,90 @@ MidiRegionView::redisplay_model()
 }
 
 void
-MidiRegionView::display_program_change_flags()
+MidiRegionView::display_program_changes()
 {
-       for (Automatable::Controls::iterator control = _model->controls().begin();
-                       control != _model->controls().end(); ++control) {
-               if (control->first.type() == MidiPgmChangeAutomation) {
-                       Glib::Mutex::Lock list_lock (control->second->list()->lock());
+       boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
+       if (!control) {
+               return;
+       }
 
-                       uint8_t channel = control->first.channel();
-                       
-                       for (AutomationList::const_iterator event = control->second->list()->begin();
-                                       event != control->second->list()->end(); ++event) {
-                               double event_time     = (*event)->when;
-                               double program_number = floor((*event)->value + 0.5);
-
-                               //cerr << " got program change on channel " << int(channel)
-                               //              << " time: " << event_time << " number: " << program_number << endl;
-                               
-                               // find bank select msb and lsb for the program change                          
-                               Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
-                               boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
-                               uint8_t msb = 0;
-                               if (msb_control != 0) {
-                                       msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
-                               }
+       Glib::Mutex::Lock lock (control->list()->lock());
 
-                               Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
-                               boost::shared_ptr<Evoral::Control>  lsb_control = _model->control(bank_select_lsb);
-                               uint8_t lsb = 0;
-                               if (lsb_control != 0) {
-                                       lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
-                               }
-                                       
-                               //cerr << " got msb " << int(msb) << " and lsb " << int(lsb)
-                               //              << " thread_id: " << pthread_self() << endl;
-                                       
-                               MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
-                               
-                               boost::shared_ptr<MIDI::Name::Patch> patch = 
-                                       MIDI::Name::MidiPatchManager::instance().find_patch(
-                                                       _model_name,
-                                                       _custom_device_mode, 
-                                                       channel, 
-                                                       patch_key);
-                               
-                               ControlEvent program_change(beats_to_frames(event_time),
-                                               uint8_t(program_number), channel);
-                               
-                               if (patch != 0) {
-                                       //cerr << " got patch with name " << patch->name()
-                                       //              << " number " << patch->number() << endl;
-                                       add_pgm_change(program_change, patch->name());
-                               } else {
-                                       char buf[4];
-                                       snprintf(buf, 4, "%d", int(program_number));
-                                       add_pgm_change(program_change, buf);
-                               }
+       uint8_t channel = control->parameter().channel();
+
+       for (AutomationList::const_iterator event = control->list()->begin();
+                       event != control->list()->end(); ++event) {
+               double event_time     = (*event)->when;
+               double program_number = floor((*event)->value + 0.5);
+
+               // Get current value of bank select MSB at time of the program change
+               Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
+               boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
+               uint8_t msb = 0;
+               if (msb_control != 0) {
+                       msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
+               }
+
+               // Get current value of bank select LSB at time of the program change
+               Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
+               boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
+               uint8_t lsb = 0;
+               if (lsb_control != 0) {
+                       lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
+               }
+
+               MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
+
+               boost::shared_ptr<MIDI::Name::Patch> patch = 
+                       MIDI::Name::MidiPatchManager::instance().find_patch(
+                                       _model_name, _custom_device_mode, channel, patch_key);
+
+               PCEvent program_change(event_time, uint8_t(program_number), channel);
+
+               if (patch != 0) {
+                       add_pgm_change(program_change, patch->name());
+               } else {
+                       char buf[4];
+                       snprintf(buf, 4, "%d", int(program_number));
+                       add_pgm_change(program_change, buf);
+               }
+       }
+}
+
+void 
+MidiRegionView::display_sysexes()
+{
+       for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
+               ARDOUR::MidiModel::TimeType time = (*i)->time();
+               assert(time >= 0);
+               
+               ostringstream str;
+               str << hex;
+               for (uint32_t b = 0; b < (*i)->size(); ++b) {
+                       str << int((*i)->buffer()[b]);
+                       if (b != (*i)->size() -1) {
+                               str << " ";
                        }
-                       break;
-               } else if (control->first.type() == MidiCCAutomation) {
-                       //cerr << " found CC Automation of channel " << int(control->first.channel())
-                       //              << " and id " << control->first.id() << endl;
                }
-       }       
+               string text = str.str();
+               
+               ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
+               const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
+               
+               double height = midi_stream_view()->contents_height();
+               
+               boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
+                               new CanvasSysEx(*this, *group, text, height, x, 1.0));
+               
+               // Show unless program change is beyond the region bounds
+               if (time - _region->start() >= _region->length() || time < _region->start()) {
+                       sysex->hide();
+               } else {
+                       sysex->show();
+               }
+               
+               _sys_exes.push_back(sysex);
+       }
 }
 
 
@@ -672,6 +671,7 @@ MidiRegionView::region_resized (Change what_changed)
        RegionView::region_resized(what_changed);
        
        if (what_changed & ARDOUR::PositionChanged) {
+               set_duration(_region->length(), 0);
                if (_enable_display) {
                        redisplay_model();
                }
@@ -701,8 +701,8 @@ MidiRegionView::set_height (double height)
                         midi_stream_view()->highest_note(),
                         height != old_height + FUDGE);
        
-       if (name_text) {
-               name_text->raise_to_top();
+       if (name_pixbuf) {
+               name_pixbuf->raise_to_top();
        }
 }
 
@@ -713,71 +713,67 @@ MidiRegionView::set_height (double height)
 void
 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
 {
-       if (_enable_display) {
-               if (!force && _current_range_min == min && _current_range_max == max) {
-                       return;
-               }
-               
-               _current_range_min = min;
-               _current_range_max = max;
-
-               for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
-                       CanvasNoteEvent* event = *i;
-                       Item* item = dynamic_cast<Item*>(event);
-                       assert(item);
-                       if (event && event->note()) {
-                               if (event->note()->note() < _current_range_min
-                                               || event->note()->note() > _current_range_max) {
-                                       if (canvas_item_visible(item)) {
-                                               item->hide();
-                                       }
-                               } else {
-                                       if (!canvas_item_visible(item)) {
-                                               item->show();
-                                       }
-
-                                       event->hide_velocity();
-                                       if (CanvasNote* note = dynamic_cast<CanvasNote*>(event)) {
-                                               const double y1 = midi_stream_view()->note_to_y(event->note()->note());
-                                               const double y2 = y1 + floor(midi_stream_view()->note_height());
-
-                                               note->property_y1() = y1;
-                                               note->property_y2() = y2;
-                                       } else if (CanvasHit* hit = dynamic_cast<CanvasHit*>(event)) {
-                                               double x = trackview.editor().frame_to_pixel(
-                                                               beats_to_frames(event->note()->time()) - _region->start());
-                                               const double diamond_size = midi_stream_view()->note_height() / 2.0;
-                                               double y = midi_stream_view()->note_to_y(event->note()->note()) 
-                                                                + ((diamond_size-2.0) / 4.0);
-                                               
-                                               hit->set_height(diamond_size);
-                                               hit->move(x-hit->x1(), y-hit->y1());
-                                               hit->show();
-                                       }
-                                       if (event->selected()) {
-                                               event->show_velocity();
-                                       }
+       if (!_enable_display) {
+               return;
+       }
+
+       if (!force && _current_range_min == min && _current_range_max == max) {
+               return;
+       }
+       
+       _current_range_min = min;
+       _current_range_max = max;
+
+       for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
+               CanvasNoteEvent* event = *i;
+               Item* item = dynamic_cast<Item*>(event);
+               assert(item);
+               if (event && event->note()) {
+                       if (event->note()->note() < _current_range_min
+                                       || event->note()->note() > _current_range_max) {
+                               if (canvas_item_visible(item)) {
+                                       item->hide();
+                               }
+                       } else {
+                               if (!canvas_item_visible(item)) {
+                                       item->show();
+                               }
+
+                               if (CanvasNote* note = dynamic_cast<CanvasNote*>(event)) {
+                                       const double y1 = midi_stream_view()->note_to_y(event->note()->note());
+                                       const double y2 = y1 + floor(midi_stream_view()->note_height());
+
+                                       note->property_y1() = y1;
+                                       note->property_y2() = y2;
+                               } else if (CanvasHit* hit = dynamic_cast<CanvasHit*>(event)) {
+                                       double x = trackview.editor().frame_to_pixel(
+                                                       beats_to_frames(event->note()->time()) - _region->start());
+                                       const double diamond_size = midi_stream_view()->note_height() / 2.0;
+                                       double y = midi_stream_view()->note_to_y(event->note()->note()) 
+                                                        + ((diamond_size-2.0) / 4.0);
+                                       
+                                       hit->set_height(diamond_size);
+                                       hit->move(x-hit->x1(), y-hit->y1());
+                                       hit->show();
                                }
                        }
                }
        }
+       
 }
 
 GhostRegion*
 MidiRegionView::add_ghost (TimeAxisView& tv)
 {
-       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&trackview);
        CanvasNote* note;
-       assert(rtv);
 
        double unit_position = _region->position () / samples_per_unit;
        MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
        MidiGhostRegion* ghost;
 
        if (mtv && mtv->midi_view()) {
-               /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group.
-                  this is because it's nice to have midi notes on top of the note lines and
-                  audio waveforms under it.
+               /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
+                  to allow having midi notes on top of note lines and waveforms.
                 */
                ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
        } else {
@@ -821,6 +817,7 @@ MidiRegionView::end_write()
        delete[] _active_notes;
        _active_notes = NULL;
        _marked_for_selection.clear();
+       _marked_for_velocity.clear();
 }
 
 
@@ -834,7 +831,8 @@ MidiRegionView::resolve_note(uint8_t note, double end_time)
        }
 
        if (_active_notes && _active_notes[note]) {
-               _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel((nframes64_t)end_time);
+               const nframes64_t end_time_frames = beats_to_frames(end_time);
+               _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
                _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
                _active_notes[note] = NULL;
        }
@@ -944,10 +942,7 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
                                if (_active_notes[note->note()]) {
                                        CanvasNote* const old_rect = _active_notes[note->note()];
                                        boost::shared_ptr<NoteType> old_note = old_rect->note();
-                                       cerr << "MidiModel: WARNING: Note has length 0: chan " << old_note->channel()
-                                               << "note " << (int)old_note->note() << " @ " << old_note->time() << endl;
-                                       /* FIXME: How large to make it?  Make it a diamond? */
-                                       old_rect->property_x2() = old_rect->property_x1() + 2.0;
+                                       old_rect->property_x2() = x;
                                        old_rect->property_outline_what() = (guint32) 0xF;
                                }
                                _active_notes[note->note()] = ev_rect;
@@ -983,6 +978,9 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
                if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
                        note_selected(event, true);
                }
+               if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
+                       event->show_velocity();
+               }
                event->on_channel_selection_change(_last_channel_selection);
                _events.push_back(event);
                if (note_in_visible_range(note)) {
@@ -994,32 +992,30 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
 }
 
 void
-MidiRegionView::add_pgm_change(ControlEvent& program, string displaytext)
+MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
 {
        assert(program.time >= 0);
        
-       // dont display program changes beyond the region bounds
-       if (program.time - _region->start() >= _region->length() || program.time <  _region->start()) 
-               return;
-       
        ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
-       const double x = trackview.editor().frame_to_pixel((nframes64_t)program.time - _region->start());
+       const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
        
        double height = midi_stream_view()->contents_height();
        
        boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
-                       new CanvasProgramChange(
-                                       *this, 
-                                       *group, 
+                       new CanvasProgramChange(*this, *group,
                                        displaytext, 
                                        height, 
-                                       x, 
-                                       1.0, 
+                                       x, 1.0, 
                                        _model_name, 
                                        _custom_device_mode, 
-                                       program.time, 
-                                       program.channel, 
-                                       program.value));
+                                       program.time, program.channel, program.value));
+       
+       // Show unless program change is beyond the region bounds
+       if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
+               pgm_change->hide();
+       } else {
+               pgm_change->show();
+       }
        
        _pgm_changes.push_back(pgm_change);
 }
@@ -1029,7 +1025,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
 {
        cerr << "getting patch key at " << time << " for channel " << channel << endl;
        Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
-       boost::shared_ptr<Evoral::Control>  msb_control = _model->control(bank_select_msb);
+       boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
        float msb = -1.0;
        if (msb_control != 0) {
                msb = int(msb_control->get_float(true, time));
@@ -1037,7 +1033,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
        }
 
        Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
-       boost::shared_ptr<Evoral::Control>  lsb_control = _model->control(bank_select_lsb);
+       boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
        float lsb = -1.0;
        if (lsb_control != 0) {
                lsb = lsb_control->get_float(true, time);
@@ -1045,7 +1041,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
        }
        
        Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
-       boost::shared_ptr<Evoral::Control>  program_control = _model->control(program_change);
+       boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
        float program_number = -1.0;
        if (program_control != 0) {
                program_number = program_control->get_float(true, time);
@@ -1060,24 +1056,24 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
 
 
 void 
-MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
+MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
 {
        // TODO: Get the real event here and alter them at the original times
        Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
-       boost::shared_ptr<Evoral::Control>  msb_control = _model->control(bank_select_msb);
+       boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
        if (msb_control != 0) {
                msb_control->set_float(float(new_patch.msb), true, old_program.time);
        }
 
        // TODO: Get the real event here and alter them at the original times
        Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
-       boost::shared_ptr<Evoral::Control>  lsb_control = _model->control(bank_select_lsb);
+       boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
        if (lsb_control != 0) {
                lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
        }
        
        Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
-       boost::shared_ptr<Evoral::Control>  program_control = _model->control(program_change);
+       boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
        
        assert(program_control != 0);
        program_control->set_float(float(new_patch.program_number), true, old_program.time);
@@ -1088,7 +1084,7 @@ MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name
 void
 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
 {
-       ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
+       PCEvent program_change_event(program.event_time(), program.program(), program.channel());
        alter_program_change(program_change_event, new_patch);
 }
 
@@ -1105,7 +1101,7 @@ MidiRegionView::previous_program(CanvasProgramChange& program)
                                program.channel(), 
                                key);
        
-       ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
+       PCEvent program_change_event(program.event_time(), program.program(), program.channel());
        if (patch) {
                alter_program_change(program_change_event, patch->patch_primary_key());
        }
@@ -1123,7 +1119,8 @@ MidiRegionView::next_program(CanvasProgramChange& program)
                                _custom_device_mode, 
                                program.channel(), 
                                key);   
-       ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
+
+       PCEvent program_change_event(program.event_time(), program.program(), program.channel());
        if (patch) {
                alter_program_change(program_change_event, patch->patch_primary_key());
        }
@@ -1132,7 +1129,13 @@ MidiRegionView::next_program(CanvasProgramChange& program)
 void
 MidiRegionView::delete_selection()
 {
-       assert(_delta_command);
+       if (_selection.empty()) {
+               return;
+       }
+
+       if (!_delta_command) {
+               _delta_command = _model->new_delta_command("delete selection");
+       }
 
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                if ((*i)->selected()) {
@@ -1149,6 +1152,7 @@ MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                if ((*i)->selected() && (*i) != ev) {
                        (*i)->selected(false);
+                       (*i)->hide_velocity();
                }
        }
 
@@ -1161,6 +1165,7 @@ MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                if ((*i) != ev) {
                        (*i)->selected(false);
+                       (*i)->hide_velocity();
                }
        }
 
@@ -1265,8 +1270,9 @@ MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2
 void
 MidiRegionView::move_selection(double dx, double dy)
 {
-       for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i)
+       for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
                (*i)->move_event(dx, dy);
+       }
 }
 
 
@@ -1274,88 +1280,89 @@ void
 MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
 {
        // TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy...
-       if (_selection.find(ev) != _selection.end()) {
-               uint8_t lowest_note_in_selection  = midi_stream_view()->lowest_note();
-               uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
-               uint8_t highest_note_difference = 0;
-
-               // 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;
-               */
-               
-               // Make sure the note pitch does not exceed the MIDI standard range
-               if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
-                       highest_note_difference = highest_note_in_selection - 127;
-               }
-               
-               start_delta_command(_("move notes"));
-
-               for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
-                       Selection::iterator next = i;
-                       ++next;
+       if (_selection.find(ev) == _selection.end()) {
+               return;
+       }
 
-                       const boost::shared_ptr<NoteType> copy(new NoteType(*(*i)->note().get()));
+       uint8_t lowest_note_in_selection  = midi_stream_view()->lowest_note();
+       uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
+       uint8_t highest_note_difference = 0;
 
-                       // we need to snap here again in nframes64_t in order to be sample accurate 
-                       double start_frames = snap_to_frame(beats_to_frames((*i)->note()->time()) + dt);
+       // 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;
+       */
+       
+       // Make sure the note pitch does not exceed the MIDI standard range
+       if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
+               highest_note_difference = highest_note_in_selection - 127;
+       }
+       
+       start_delta_command(_("move notes"));
 
-                       // keep notes inside region if dragged beyond left region bound
-                       if (start_frames < _region->start()) {                          
-                               start_frames = _region->start();
-                       }
-                       
-                       copy->set_time(frames_to_beats(start_frames));
+       for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
+               Selection::iterator next = i;
+               ++next;
 
-                       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 original pitch if note is dragged outside valid midi range
-                       if ((original_pitch != 0 && new_pitch == 0)
-                                       || (original_pitch != 127 && new_pitch == 127)) {
-                               new_pitch = original_pitch;
-                       }
+               const boost::shared_ptr<NoteType> copy(new NoteType(*(*i)->note().get()));
 
-                       lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
-                       highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+               nframes64_t start_frames = beats_to_frames((*i)->note()->time());
+               if (dt >= 0) {
+                       start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
+               } else {
+                       start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
+               }
 
-                       copy->set_note(new_pitch);
-                       
-                       command_remove_note(*i);
-                       command_add_note(copy, (*i)->selected());
+               copy->set_time(frames_to_beats(start_frames));
 
-                       i = next;
+               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 original pitch if note is dragged outside valid midi range
+               if ((original_pitch != 0 && new_pitch == 0)
+                               || (original_pitch != 127 && new_pitch == 127)) {
+                       new_pitch = original_pitch;
                }
 
-               apply_command();
+               lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
+               highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+
+               copy->set_note(new_pitch);
                
-               // 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);
-               }
+               command_remove_note(*i);
+               command_add_note(copy, (*i)->selected());
+
+               i = next;
+       }
+
+       apply_command();
+       
+       // 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);
        }
 }
 
 nframes64_t
-MidiRegionView::snap_to_frame(double x)
+MidiRegionView::snap_pixel_to_frame(double x)
 {
-       PublicEditor &editor = trackview.editor();
+       PublicEditoreditor = trackview.editor();
        // x is region relative, convert it to global absolute frames
        nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
        editor.snap_to(frame);
@@ -1363,22 +1370,19 @@ MidiRegionView::snap_to_frame(double x)
 }
 
 nframes64_t
-MidiRegionView::snap_to_frame(nframes64_t x)
+MidiRegionView::snap_frame_to_frame(nframes64_t x)
 {
-       PublicEditor &editor = trackview.editor();
-       // x is region relative
-       // convert x to global frame
+       PublicEditor& editor = trackview.editor();
+       // x is region relative, convert it to global absolute frames
        nframes64_t frame = x + _region->position();
        editor.snap_to(frame);
-       // convert event_frame back to local coordinates relative to position
-       frame -= _region->position();
-       return frame;
+       return frame - _region->position(); // convert back to region relative
 }
 
 double
 MidiRegionView::snap_to_pixel(double x)
 {
-       return (double) trackview.editor().frame_to_pixel(snap_to_frame(x));
+       return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
 }
 
 double
@@ -1391,17 +1395,13 @@ MidiRegionView::get_position_pixels()
 nframes64_t
 MidiRegionView::beats_to_frames(double beats) const
 {
-       const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
-       const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
-       return lrint(beats * m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar());
+       return _time_converter.to(beats);
 }
 
 double
 MidiRegionView::frames_to_beats(nframes64_t frames) const
 {
-       const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
-       const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
-       return frames / m.frames_per_bar(t, trackview.session().frame_rate()) * m.beats_per_bar();
+       return _time_converter.from(frames);
 }
 
 void
@@ -1497,7 +1497,7 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo
 
                // because snapping works on world coordinates we have to transform current_x
                // to world coordinates before snapping and transform it back afterwards
-               nframes64_t current_frame = snap_to_frame(current_x);
+               nframes64_t current_frame = snap_pixel_to_frame(current_x);
                // transform to region start relative
                current_frame += _region->start();
                
@@ -1538,7 +1538,7 @@ MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bo
        }
 
        command_remove_note(event);
-       command_add_note(copy, event->selected());
+       command_add_note(copy, event->selected(), true);
 }
 
 void
@@ -1586,11 +1586,7 @@ MidiRegionView::change_channel(uint8_t channel)
 void
 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
 {
-       if (ev->note() && _mouse_state == EraseTouchDragging) {
-               start_delta_command(_("note entered"));
-               ev->selected(true);
-               _delta_command->remove(ev->note());
-       } else if (_mouse_state == SelectTouchDragging) {
+       if (_mouse_state == SelectTouchDragging) {
                note_selected(ev, true);
        }
 }