X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=154cd158dc25bf325300d4a98dbb0b7f389e8f02;hb=ec1ef5d6b574258d6451aa60749c4241f3c42f5f;hp=1de7f0176a7248ee3794132b3cfcbacd26f725fe;hpb=9e6eb3e0c732cc399ed3b925ef6c584edc853dc7;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 1de7f0176a..154cd158dc 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -24,7 +24,7 @@ #include -#include +#include "gtkmm2ext/gtk_ui.h" #include @@ -41,6 +41,7 @@ #include "evoral/Parameter.hpp" #include "evoral/MIDIParameters.hpp" +#include "evoral/MIDIEvent.hpp" #include "evoral/Control.hpp" #include "evoral/midi_util.h" @@ -51,6 +52,7 @@ #include "canvas_patch_change.h" #include "debug.h" #include "editor.h" +#include "editor_drag.h" #include "ghostregion.h" #include "gui_thread.h" #include "keyboard.h" @@ -61,6 +63,7 @@ #include "midi_streamview.h" #include "midi_time_axis.h" #include "midi_util.h" +#include "mouse_cursors.h" #include "note_player.h" #include "public_editor.h" #include "rgb_macros.h" @@ -68,7 +71,6 @@ #include "simpleline.h" #include "streamview.h" #include "utils.h" -#include "mouse_cursors.h" #include "patch_change_dialog.h" #include "verbose_cursor.h" @@ -80,6 +82,8 @@ using namespace Editing; using namespace ArdourCanvas; using Gtkmm2ext::Keyboard; +PBD::Signal1 MidiRegionView::SelectionCleared; + #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1) MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, @@ -89,13 +93,10 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _last_channel_selection(0xFFFF) , _current_range_min(0) , _current_range_max(0) - , _model_name(string()) - , _custom_device_mode(string()) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*group)) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) , _step_edit_cursor (0) , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) @@ -109,13 +110,16 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) - , _pre_enter_cursor (0) + , pre_enter_cursor (0) + , pre_press_cursor (0) { _note_group->raise_to_top(); PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); - Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); + Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); connect_to_diskstream (); + + SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); } MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, @@ -124,13 +128,12 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & : RegionView (parent, tv, r, spu, basic_color, false, visibility) , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _model_name(string()) - , _custom_device_mode(string()) + , _current_range_min(0) + , _current_range_max(0) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*parent)) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) , _step_edit_cursor (0) , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) @@ -144,11 +147,15 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , pre_enter_cursor (0) + , pre_press_cursor (0) { _note_group->raise_to_top(); PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); connect_to_diskstream (); + + SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); } void @@ -166,13 +173,12 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , RegionView (other) , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _model_name(string()) - , _custom_device_mode(string()) + , _current_range_min(0) + , _current_range_max(0) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*get_canvas_group())) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) , _step_edit_cursor (0) , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) @@ -186,6 +192,8 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , pre_enter_cursor (0) + , pre_press_cursor (0) { Gdk::Color c; int r,g,b,a; @@ -200,13 +208,12 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr (region)) , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _model_name(string()) - , _custom_device_mode(string()) + , _current_range_min(0) + , _current_range_max(0) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*get_canvas_group())) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) , _step_edit_cursor (0) , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) @@ -220,6 +227,8 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); + Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); connect_to_diskstream (); + + SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); +} + +const boost::shared_ptr +MidiRegionView::midi_region() const +{ + return boost::dynamic_pointer_cast(_region); } void @@ -291,7 +308,7 @@ MidiRegionView::connect_to_diskstream () { midi_view()->midi_track()->DataRecorded.connect( *this, invalidator(*this), - ui_bind(&MidiRegionView::data_recorded, this, _1, _2), + boost::bind (&MidiRegionView::data_recorded, this, _1), gui_context()); } @@ -312,6 +329,10 @@ MidiRegionView::canvas_event(GdkEvent* ev) break; } + if (ev->type == GDK_2BUTTON_PRESS) { + return trackview.editor().toggle_internal_editing_from_double_click (ev); + } + if (!trackview.editor().internal_editing()) { return false; } @@ -329,9 +350,6 @@ MidiRegionView::canvas_event(GdkEvent* ev) case GDK_BUTTON_PRESS: return button_press (&ev->button); - case GDK_2BUTTON_PRESS: - return true; - case GDK_BUTTON_RELEASE: return button_release (&ev->button); @@ -362,16 +380,20 @@ bool MidiRegionView::enter_notify (GdkEventCrossing* ev) { trackview.editor().MouseModeChanged.connect ( - _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context () - ); + _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context () + ); - Keyboard::magic_widget_grab_focus(); - group->grab_focus(); - - if (trackview.editor().current_mouse_mode() == MouseRange) { + if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) { create_ghost_note (ev->x, ev->y); } + if (!trackview.editor().internal_editing()) { + Keyboard::magic_widget_drop_focus(); + } else { + Keyboard::magic_widget_grab_focus(); + group->grab_focus(); + } + return false; } @@ -382,18 +404,26 @@ MidiRegionView::leave_notify (GdkEventCrossing*) trackview.editor().verbose_cursor()->hide (); remove_ghost_note (); + return false; } void MidiRegionView::mouse_mode_changed () { - if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) { + if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) { create_ghost_note (_last_event_x, _last_event_y); } else { remove_ghost_note (); trackview.editor().verbose_cursor()->hide (); } + + if (!trackview.editor().internal_editing()) { + Keyboard::magic_widget_drop_focus(); + } else { + Keyboard::magic_widget_grab_focus(); + group->grab_focus(); + } } bool @@ -403,16 +433,19 @@ MidiRegionView::button_press (GdkEventButton* ev) return false; } - _last_x = ev->x; - _last_y = ev->y; + Editor* editor = dynamic_cast (&trackview.editor()); + MouseMode m = editor->current_mouse_mode(); - group->w2i (_last_x, _last_y); + if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { + pre_press_cursor = editor->get_canvas_cursor (); + editor->set_canvas_cursor (editor->cursors()->midi_pencil); + } if (_mouse_state != SelectTouchDragging) { - + _pressed_button = ev->button; _mouse_state = Pressed; - + return true; } @@ -436,10 +469,22 @@ MidiRegionView::button_release (GdkEventButton* ev) group->w2i(event_x, event_y); group->ungrab(ev->time); + PublicEditor& editor = trackview.editor (); + + if (pre_press_cursor) { + dynamic_cast(&editor)->set_canvas_cursor (pre_press_cursor, false); + pre_press_cursor = 0; + } + switch (_mouse_state) { case Pressed: // Clicked - switch (trackview.editor().current_mouse_mode()) { + switch (editor.current_mouse_mode()) { + case MouseRange: + /* no motion occured - simple click */ + clear_selection (); + break; + case MouseObject: case MouseTimeFX: { @@ -454,27 +499,37 @@ MidiRegionView::button_release (GdkEventButton* ev) group->w2i(event_x, event_y); bool success; - Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x)); + Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x)); if (!success) { beats = 1; } - create_note_at (event_x, event_y, beats, true); + /* Shorten the length by 1 tick so that we can add a new note at the next + grid snap without it overlapping this one. + */ + beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat; + + create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true); } break; } - case MouseRange: + case MouseDraw: { bool success; - Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x)); + Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x)); if (!success) { beats = 1; } - create_note_at (event_x, event_y, beats, true); + /* Shorten the length by 1 tick so that we can add a new note at the next + grid snap without it overlapping this one. + */ + beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat; + + create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true); break; } @@ -485,32 +540,13 @@ MidiRegionView::button_release (GdkEventButton* ev) _mouse_state = None; break; - case SelectRectDragging: // Select drag done - + case SelectRectDragging: + case AddDragging: + editor.drags()->end_grab ((GdkEvent *) ev); _mouse_state = None; - delete _drag_rect; - _drag_rect = 0; + create_ghost_note (ev->x, ev->y); break; - case AddDragging: // Add drag done - - _mouse_state = None; - - if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) { - - if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) { - - const double x = _drag_rect->property_x1(); - const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1()); - - create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), true); - } - } - - delete _drag_rect; - _drag_rect = 0; - - create_ghost_note (ev->x, ev->y); default: break; @@ -522,32 +558,26 @@ MidiRegionView::button_release (GdkEventButton* ev) bool MidiRegionView::motion (GdkEventMotion* ev) { - double event_x, event_y; - framepos_t event_frame = 0; - - event_x = ev->x; - event_y = ev->y; - group->w2i(event_x, event_y); - - // convert event_x to global frame - event_frame = snap_pixel_to_frame (event_x); + PublicEditor& editor = trackview.editor (); - if (!_ghost_note && trackview.editor().current_mouse_mode() != MouseRange - && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) - && _mouse_state != AddDragging) { + if (!_ghost_note && editor.current_mouse_mode() == MouseObject && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) && + _mouse_state != AddDragging) { create_ghost_note (ev->x, ev->y); - } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange - && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { + + } else if (_ghost_note && editor.current_mouse_mode() == MouseObject && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { update_ghost_note (ev->x, ev->y); - } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange) { - delete _ghost_note; - _ghost_note = 0; + } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) { + + remove_ghost_note (); + editor.verbose_cursor()->hide (); + + } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) { - trackview.editor().verbose_cursor()->hide (); - } else if (_ghost_note && trackview.editor().current_mouse_mode() == MouseRange) { update_ghost_note (ev->x, ev->y); } @@ -558,121 +588,39 @@ MidiRegionView::motion (GdkEventMotion* ev) } switch (_mouse_state) { - case Pressed: // Maybe start a drag, if we've moved a bit - - if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) { - /* no appreciable movement since the button was pressed */ - return false; - } - - if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject - && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { - // Select drag start - - group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, - Gdk::Cursor(Gdk::FLEUR), ev->time); - - _last_x = event_x; - _last_y = event_y; - _drag_start_x = event_x; - _drag_start_y = event_y; - - _drag_rect = new ArdourCanvas::SimpleRect(*group); - _drag_rect->property_x1() = event_x; - _drag_rect->property_y1() = event_y; - _drag_rect->property_x2() = event_x; - _drag_rect->property_y2() = event_y; - _drag_rect->property_outline_what() = 0xFF; - _drag_rect->property_outline_color_rgba() - = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get(); - _drag_rect->property_fill_color_rgba() - = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get(); - - _mouse_state = SelectRectDragging; - return true; - - } else if (trackview.editor().internal_editing()) { - // Add note drag start - - delete _ghost_note; - _ghost_note = 0; - - group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, - Gdk::Cursor(Gdk::FLEUR), ev->time); - - _last_x = event_x; - _last_y = event_y; - _drag_start_x = event_x; - _drag_start_y = event_y; - - _drag_rect = new ArdourCanvas::SimpleRect(*group); - _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame); - - _drag_rect->property_y1() = midi_stream_view()->note_to_y( - midi_stream_view()->y_to_note(event_y)); - _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame); - _drag_rect->property_y2() = _drag_rect->property_y1() - + floor(midi_stream_view()->note_height()); - _drag_rect->property_outline_what() = 0xFF; - _drag_rect->property_outline_color_rgba() = 0xFFFFFF99; - _drag_rect->property_fill_color_rgba() = 0xFFFFFF66; - - _mouse_state = AddDragging; - - if (_ghost_note) { - - delete _ghost_note; - _ghost_note = 0; - - trackview.editor().verbose_cursor()->hide (); + case Pressed: + + if (_pressed_button == 1) { + + MouseMode m = editor.current_mouse_mode(); + + if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) { + + editor.drags()->set (new NoteCreateDrag (dynamic_cast (&editor), group, this), (GdkEvent *) ev); + _mouse_state = AddDragging; + remove_ghost_note (); + editor.verbose_cursor()->hide (); + return true; + } else if (m == MouseObject) { + + editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); + _mouse_state = SelectRectDragging; + return true; + } else if (m == MouseRange) { + editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); + _mouse_state = SelectVerticalDragging; + return true; } - - return true; } return false; - case SelectRectDragging: // Select drag motion - case AddDragging: // Add note drag motion - - if (ev->is_hint) { - int t_x; - int t_y; - GdkModifierType state; - gdk_window_get_pointer(ev->window, &t_x, &t_y, &state); - event_x = t_x; - event_y = t_y; - } - - if (_mouse_state == AddDragging) { - event_x = trackview.editor().frame_to_pixel(event_frame); - } - - if (_drag_rect) { - - if (event_x > _drag_start_x) { - _drag_rect->property_x2() = event_x; - } - else { - _drag_rect->property_x1() = event_x; - } - } - - if (_drag_rect && _mouse_state == SelectRectDragging) { - - if (event_y > _drag_start_y) { - _drag_rect->property_y2() = event_y; - } - else { - _drag_rect->property_y1() = event_y; - } - - update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y); - } - - _last_x = event_x; - _last_y = event_y; - + case SelectRectDragging: + case SelectVerticalDragging: + case AddDragging: + editor.drags()->motion_handler ((GdkEvent *) ev, false); + break; + case SelectTouchDragging: return false; @@ -691,6 +639,13 @@ MidiRegionView::scroll (GdkEventScroll* ev) return false; } + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + /* XXX: bit of a hack; allow PrimaryModifier scroll through so that + it still works for zoom. + */ + return false; + } + trackview.editor().verbose_cursor()->hide (); bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier); @@ -711,15 +666,17 @@ MidiRegionView::key_press (GdkEventKey* ev) repeated presses, carry out key actions at key press, not release. */ - if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) { + bool unmodified = Keyboard::no_modifier_keys_pressed (ev); + + if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) { _mouse_state = SelectTouchDragging; return true; - } else if (ev->keyval == GDK_Escape) { + } else if (ev->keyval == GDK_Escape && unmodified) { clear_selection(); _mouse_state = None; - } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) { + } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) { bool start = (ev->keyval == GDK_comma); bool end = (ev->keyval == GDK_period); @@ -730,7 +687,7 @@ MidiRegionView::key_press (GdkEventKey* ev) return true; - } else if (ev->keyval == GDK_Delete) { + } else if (ev->keyval == GDK_Delete && unmodified) { delete_selection(); return true; @@ -781,20 +738,17 @@ MidiRegionView::key_press (GdkEventKey* ev) } return true; - } else if (ev->keyval == GDK_Left) { + } else if (ev->keyval == GDK_Left && unmodified) { nudge_notes (false); return true; - } else if (ev->keyval == GDK_Right) { + } else if (ev->keyval == GDK_Right && unmodified) { nudge_notes (true); return true; - } else if (ev->keyval == GDK_Control_L) { - return true; - - } else if (ev->keyval == GDK_c) { + } else if (ev->keyval == GDK_c && unmodified) { channel_edit (); return true; } @@ -805,7 +759,7 @@ MidiRegionView::key_press (GdkEventKey* ev) bool MidiRegionView::key_release (GdkEventKey* ev) { - if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) { + if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) { _mouse_state = None; return true; } @@ -816,7 +770,7 @@ void MidiRegionView::channel_edit () { bool first = true; - uint8_t current_channel; + uint8_t current_channel = 0; if (_selection.empty()) { return; @@ -857,19 +811,19 @@ void MidiRegionView::show_list_editor () { if (!_list_editor) { - _list_editor = new MidiListEditor (trackview.session(), midi_region()); + _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track()); } _list_editor->present (); } /** Add a note to the model, and the view, at a canvas (click) coordinate. - * \param x horizontal position in pixels + * \param t time in frames relative to the position of the region * \param y vertical position in pixels - * \param length duration of the note in beats, which will be snapped to the grid - * \param sh true to make the note 1 frame shorter than the snapped version of \a length. + * \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(double x, double y, double length, bool sh) +MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t) { MidiTimeAxisView* const mtv = dynamic_cast(&trackview); MidiStreamView* const view = mtv->midi_view(); @@ -880,21 +834,17 @@ MidiRegionView::create_note_at(double x, double y, double length, bool sh) assert(note <= 127.0); // Start of note in frames relative to region start - framepos_t const start_frames = snap_pixel_to_frame (x); - assert(start_frames >= 0); - - // Snap length - length = frames_to_beats( - snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames); + if (snap_t) { + framecnt_t grid_frames; + t = snap_frame_to_grid_underneath (t, grid_frames); + } + assert (t >= 0); assert (length != 0); - if (sh) { - length = frames_to_beats (beats_to_frames (length) - 1); - } - const boost::shared_ptr new_note (new NoteType (mtv->get_channel_for_add (), - frames_to_beats(start_frames + _region->start()), length, + region_frames_to_region_beats(t + _region->start()), + length, (uint8_t)note, 0x40)); if (_model->contains (new_note)) { @@ -1138,8 +1088,17 @@ MidiRegionView::redisplay_model() for (Events::iterator i = _events.begin(); i != _events.end(); ) { if (!(*i)->valid ()) { + + for (vector::iterator j = ghosts.begin(); j != ghosts.end(); ++j) { + MidiGhostRegion* gr = dynamic_cast (*j); + if (gr) { + gr->remove_note (*i); + } + } + delete *i; i = _events.erase (i); + } else { ++i; } @@ -1205,9 +1164,58 @@ MidiRegionView::display_patch_changes_on_channel (uint8_t channel) void MidiRegionView::display_sysexes() { + bool have_periodic_system_messages = false; + bool display_periodic_messages = true; + + if (!Config->get_never_display_periodic_midi()) { + + for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) { + const boost::shared_ptr > mev = + boost::static_pointer_cast > (*i); + + if (mev) { + if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { + have_periodic_system_messages = true; + break; + } + } + } + + if (have_periodic_system_messages) { + double zoom = trackview.editor().get_current_zoom (); // frames per pixel + + /* get an approximate value for the number of samples per video frame */ + + double video_frame = trackview.session()->frame_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 + * show periodic sysex messages. + */ + + if (zoom > (video_frame*4)) { + display_periodic_messages = false; + } + } + } else { + display_periodic_messages = false; + } + for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) { + + const boost::shared_ptr > mev = + boost::static_pointer_cast > (*i); + Evoral::MusicalTime time = (*i)->time(); - assert(time >= 0); + assert (time >= 0); + + if (mev) { + if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { + if (!display_periodic_messages) { + continue; + } + } + } ostringstream str; str << hex; @@ -1219,14 +1227,14 @@ MidiRegionView::display_sysexes() } string text = str.str(); - const double x = trackview.editor().frame_to_pixel(beats_to_frames(time)); + const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time)); double height = midi_stream_view()->contents_height(); boost::shared_ptr sysex = boost::shared_ptr( new CanvasSysEx(*this, *_note_group, text, height, x, 1.0)); - // Show unless patch change is beyond the region bounds + // Show unless message is beyond the region bounds if (time - _region->start() >= _region->length() || time < _region->start()) { sysex->hide(); } else { @@ -1237,7 +1245,6 @@ MidiRegionView::display_sysexes() } } - MidiRegionView::~MidiRegionView () { in_destructor = true; @@ -1389,7 +1396,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv) ghost->set_duration (_region->length() / samples_per_unit); ghosts.push_back (ghost); - GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context()); + GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context()); return ghost; } @@ -1431,7 +1438,11 @@ MidiRegionView::resolve_note(uint8_t note, double end_time) if (_active_notes && _active_notes[note]) { - const framepos_t end_time_frames = beats_to_frames(end_time); + /* XXX is end_time really region-centric? I think so, because + this is a new region that we're recording, so source zero is + the same as region zero + */ + const framepos_t end_time_frames = region_beats_to_region_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 @@ -1460,7 +1471,7 @@ MidiRegionView::extend_active_notes() void MidiRegionView::play_midi_note(boost::shared_ptr note) { - if (_no_sound_notes || !trackview.editor().sound_notes()) { + if (_no_sound_notes || !Config->get_sound_midi_notes()) { return; } @@ -1478,7 +1489,7 @@ MidiRegionView::play_midi_note(boost::shared_ptr note) void MidiRegionView::play_midi_chord (vector > notes) { - if (_no_sound_notes || !trackview.editor().sound_notes()) { + if (_no_sound_notes || !Config->get_sound_midi_notes()) { return; } @@ -1499,12 +1510,10 @@ MidiRegionView::play_midi_chord (vector > notes) bool -MidiRegionView::note_in_region_range(const boost::shared_ptr note, bool& visible) const +MidiRegionView::note_in_region_range (const boost::shared_ptr note, bool& visible) const { - const framepos_t note_start_frames = beats_to_frames(note->time()); - - bool outside = (note_start_frames - _region->start() >= _region->length()) || - (note_start_frames < _region->start()); + const framepos_t note_start_frames = source_beats_to_region_frames (note->time()); + bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame()); visible = (note->note() >= midi_stream_view()->lowest_note()) && (note->note() <= midi_stream_view()->highest_note()); @@ -1520,23 +1529,19 @@ void MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions) { boost::shared_ptr note = ev->note(); - - const framepos_t note_start_frames = beats_to_frames(note->time()); - - /* trim note display to not overlap the end of its region */ - const framepos_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length()); - - const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start()); + const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time())); const double y1 = midi_stream_view()->note_to_y(note->note()); - const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start()); ev->property_x1() = x; ev->property_y1() = y1; + /* trim note display to not overlap the end of its region */ + if (note->length() > 0) { - ev->property_x2() = note_endpixel; + const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length()); + ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames); } else { - ev->property_x2() = trackview.editor().frame_to_pixel(_region->length()); + ev->property_x2() = trackview.editor().frame_to_pixel (_region->length()); } ev->property_y2() = y1 + floor(midi_stream_view()->note_height()); @@ -1576,8 +1581,8 @@ MidiRegionView::update_hit (CanvasHit* ev) { boost::shared_ptr note = ev->note(); - const framepos_t note_start_frames = beats_to_frames(note->time()); - const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start()); + const framepos_t note_start_frames = source_beats_to_region_frames(note->time()); + const double x = trackview.editor().frame_to_pixel(note_start_frames); const double diamond_size = midi_stream_view()->note_height() / 2.0; const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0); @@ -1622,7 +1627,7 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) const double diamond_size = midi_stream_view()->note_height() / 2.0; - CanvasHit* ev_diamond = new CanvasHit(*this, *_note_group, diamond_size, note); + CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note); update_hit (ev_diamond); @@ -1654,7 +1659,7 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) MidiTimeAxisView* const mtv = dynamic_cast(&trackview); MidiStreamView* const view = mtv->midi_view(); - view->update_note_range(note->note()); + view->update_note_range (note->note()); } void @@ -1665,8 +1670,8 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity /* potentially extend region to hold new note */ - framepos_t end_frame = _region->position() + beats_to_frames (new_note->end_time()); - framepos_t region_end = _region->position() + _region->length() - 1; + framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time()); + framepos_t region_end = _region->last_frame(); if (end_frame > region_end) { _region->set_length (end_frame - _region->position()); @@ -1698,7 +1703,8 @@ MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const { assert (patch->time() >= 0); - const double x = trackview.editor().frame_to_pixel (beats_to_frames (patch->time())); + framecnt_t region_frames = source_beats_to_region_frames (patch->time()); + const double x = trackview.editor().frame_to_pixel (region_frames); double const height = midi_stream_view()->contents_height(); @@ -1713,7 +1719,7 @@ MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const ); // Show unless patch change is beyond the region bounds - if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) { + if (region_frames < 0 || region_frames >= _region->length()) { patch_change->hide(); } else { patch_change->show(); @@ -1802,9 +1808,11 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChangenew_patch_change_diff_command (_("add patch change")); c->add (MidiModel::PatchChangePtr ( new Evoral::PatchChange ( - frames_to_beats (t + midi_region()->start()), mtv->get_channel_for_add(), patch.program(), patch.bank() - ) - )); + absolute_frames_to_source_beats (_region->position() + t), + mtv->get_channel_for_add(), patch.program(), patch.bank() + ) + ) + ); _model->apply_command (*trackview.session(), c); @@ -1935,40 +1943,44 @@ MidiRegionView::delete_note (boost::shared_ptr n) } void -MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev) +MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal) { - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - if ((*i)->selected() && (*i) != ev) { - (*i)->set_selected(false); - (*i)->hide_velocity(); - } - } - - _selection.clear(); -} + bool changed = false; -void -MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev) -{ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { if ((*i) != ev) { - Selection::iterator tmp = i; ++tmp; (*i)->set_selected (false); + (*i)->hide_velocity (); _selection.erase (i); + changed = true; i = tmp; - } else { ++i; } } - /* don't bother with removing this regionview from the editor selection, - since we're about to add another note, and thus put/keep this - regionview in the editor selection. + /* this does not change the status of this regionview w.r.t the editor + selection. + */ + + if (changed && signal) { + SelectionCleared (this); /* EMIT SIGNAL */ + } +} + +void +MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev) +{ + clear_selection_except (ev); + + /* don't bother with checking to see if we should remove this + regionview from the editor selection, since we're about to add + another note, and thus put/keep this regionview in the editor + selection anyway. */ if (!ev->selected()) { @@ -1986,6 +1998,31 @@ MidiRegionView::select_all_notes () } } +void +MidiRegionView::select_range (framepos_t start, framepos_t end) +{ + clear_selection (); + + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + framepos_t t = source_beats_to_absolute_frames((*i)->note()->time()); + if (t >= start && t <= end) { + add_to_selection (*i); + } + } +} + +void +MidiRegionView::invert_selection () +{ + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + if ((*i)->selected()) { + remove_from_selection(*i); + } else { + add_to_selection (*i); + } + } +} + void MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend) { @@ -2076,10 +2113,14 @@ MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask) } void -MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend) +MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend) { if (!add) { - clear_selection_except(ev); + clear_selection_except (ev); + if (!_selection.empty()) { + PublicEditor& editor (trackview.editor()); + editor.get_selection().add (this); + } } if (!extend) { @@ -2131,7 +2172,7 @@ MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev) } void -MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2) +MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend) { if (x1 > x2) { swap (x1, x2); @@ -2168,7 +2209,39 @@ MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2 if (!(*i)->selected()) { add_to_selection (*i); } - } else if ((*i)->selected()) { + } else if ((*i)->selected() && !extend) { + // Not inside rectangle + remove_from_selection (*i); + } + } +} + +void +MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend) +{ + if (y1 > y2) { + swap (y1, y2); + } + + // TODO: Make this faster by storing the last updated selection rect, and only + // adjusting things that are in the area that appears/disappeared. + // 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) { + + /* check if any corner of the note is inside the rect + + Notes: + 1) this is computing "touched by", not "contained by" the rect. + 2) this does not require that events be sorted in time. + */ + + if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) { + // within y- (note-) range + if (!(*i)->selected()) { + add_to_selection (*i); + } + } else if ((*i)->selected() && !extend) { // Not inside rectangle remove_from_selection (*i); } @@ -2233,7 +2306,7 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy) (*i)->move_event(dx, dy); } - if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) { + if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) { if (to_play.size() > 1) { @@ -2291,8 +2364,9 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote) start_note_diff_command (_("move notes")); for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { - - Evoral::MusicalTime new_time = frames_to_beats (beats_to_frames ((*i)->note()->time()) + dt); + + framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt; + Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames); if (new_time < 0) { continue; @@ -2306,12 +2380,6 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote) // 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; - } - lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); @@ -2327,6 +2395,9 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote) } } +/** @param x Pixel relative to the region position. + * @return Snapped frame relative to the region position. + */ framepos_t MidiRegionView::snap_pixel_to_frame(double x) { @@ -2334,32 +2405,9 @@ MidiRegionView::snap_pixel_to_frame(double x) return snap_frame_to_frame (editor.pixel_to_frame (x)); } -/** Snap a frame offset within our region using the current snap settings. - * @param x Frame offset from this region's position. - * @return Snapped frame offset from this region's position. +/** @param x Pixel relative to the region position. + * @return Snapped pixel relative to the region position. */ -frameoffset_t -MidiRegionView::snap_frame_to_frame (frameoffset_t x) -{ - PublicEditor& editor = trackview.editor(); - - /* x is region relative, convert it to global absolute frames */ - framepos_t const session_frame = x + _region->position(); - - /* try a snap in either direction */ - framepos_t frame = session_frame; - editor.snap_to (frame, 0); - - /* if we went off the beginning of the region, snap forwards */ - if (frame < _region->position ()) { - frame = session_frame; - editor.snap_to (frame, 1); - } - - /* back to region relative */ - return frame - _region->position(); -} - double MidiRegionView::snap_to_pixel(double x) { @@ -2381,15 +2429,37 @@ MidiRegionView::get_end_position_pixels() } framepos_t -MidiRegionView::beats_to_frames(double beats) const +MidiRegionView::source_beats_to_absolute_frames(double beats) const +{ + /* the time converter will return the frame 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(); + return source_start + _source_relative_time_converter.to (beats); +} + +double +MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const +{ + /* the `frames' argument needs to be converted into a frame 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); +} + +framepos_t +MidiRegionView::region_beats_to_region_frames(double beats) const { - return _time_converter.to(beats); + return _region_relative_time_converter.to(beats); } double -MidiRegionView::frames_to_beats(framepos_t frames) const +MidiRegionView::region_frames_to_region_beats(framepos_t frames) const { - return _time_converter.from(frames); + return _region_relative_time_converter.from(frames); } void @@ -2477,7 +2547,7 @@ MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at double beats; beats = snap_pixel_to_frame (current_x); - beats = frames_to_beats (beats); + beats = region_frames_to_region_beats (beats); double len; @@ -2539,11 +2609,11 @@ MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at } } - /* Convert that to a frame within the region */ + /* Convert that to a frame within the source */ current_x = snap_pixel_to_frame (current_x) + _region->start (); /* and then to beats */ - current_x = frames_to_beats (current_x); + current_x = region_frames_to_region_beats (current_x); if (at_front && current_x < canvas_note->note()->end_time()) { note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x); @@ -2574,6 +2644,17 @@ MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at apply_diff(); } +void +MidiRegionView::abort_resizing () +{ + for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { + delete (*i)->resize_rect; + delete *i; + } + + _resize_data.clear (); +} + void MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative) { @@ -2870,9 +2951,9 @@ MidiRegionView::nudge_notes (bool forward) into a vector and sort before using the first one. */ - framepos_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time()); + framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time()); framepos_t unused; - framepos_t distance; + framecnt_t distance; if (trackview.editor().snap_mode() == Editing::SnapOff) { @@ -2905,7 +2986,7 @@ MidiRegionView::nudge_notes (bool forward) return; } - Evoral::MusicalTime delta = frames_to_beats (fabs (distance)); + Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance)); if (!forward) { delta = -delta; @@ -2940,7 +3021,7 @@ MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev) { Editor* editor = dynamic_cast(&trackview.editor()); - _pre_enter_cursor = editor->get_canvas_cursor (); + pre_enter_cursor = editor->get_canvas_cursor (); if (_mouse_state == SelectTouchDragging) { note_selected (ev, true); @@ -2960,9 +3041,9 @@ MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*) editor->verbose_cursor()->hide (); - if (_pre_enter_cursor) { - editor->set_canvas_cursor (_pre_enter_cursor); - _pre_enter_cursor = 0; + if (pre_enter_cursor) { + editor->set_canvas_cursor (pre_enter_cursor); + pre_enter_cursor = 0; } } @@ -2985,14 +3066,16 @@ void MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor) { Editor* editor = dynamic_cast(&trackview.editor()); + Editing::MouseMode mm = editor->current_mouse_mode(); + bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw); - if (x_fraction > 0.0 && x_fraction < 0.25) { + if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) { editor->set_canvas_cursor (editor->cursors()->left_side_trim); - } else if (x_fraction >= 0.75 && x_fraction < 1.0) { + } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) { editor->set_canvas_cursor (editor->cursors()->right_side_trim); } else { - if (_pre_enter_cursor && can_set_cursor) { - editor->set_canvas_cursor (_pre_enter_cursor); + if (pre_enter_cursor && can_set_cursor) { + editor->set_canvas_cursor (pre_enter_cursor); } } } @@ -3132,7 +3215,7 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) Evoral::MusicalTime end_point = 0; duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time(); - paste_pos_beats = frames_to_beats (pos - _region->position()); + paste_pos_beats = absolute_frames_to_source_beats (pos); beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats; paste_pos_beats = 0; @@ -3162,7 +3245,7 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) /* if we pasted past the current end of the region, extend the region */ - framepos_t end_frame = _region->position() + beats_to_frames (end_point); + framepos_t end_frame = source_beats_to_absolute_frames (end_point); framepos_t region_end = _region->position() + _region->length() - 1; if (end_frame > region_end) { @@ -3170,7 +3253,7 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame)); _region->clear_changes (); - _region->set_length (end_frame); + _region->set_length (end_frame - _region->position()); trackview.session()->add_command (new StatefulDiffCommand (_region)); } @@ -3209,24 +3292,30 @@ MidiRegionView::goto_next_note (bool add_to_selection) time_sort_events (); + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + uint16_t const channel_mask = mtv->channel_selector().get_selected_channels (); + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { if ((*i)->selected()) { use_next = true; continue; } else if (use_next) { - if (!add_to_selection) { - unique_select (*i); - } else { - note_selected (*i, true, false); + if (channel_mask & (1 << (*i)->note()->channel())) { + if (!add_to_selection) { + unique_select (*i); + } else { + note_selected (*i, true, false); + } + return; } - return; } } /* use the first one */ - unique_select (_events.front()); - + if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) { + unique_select (_events.front()); + } } void @@ -3240,23 +3329,30 @@ MidiRegionView::goto_previous_note (bool add_to_selection) time_sort_events (); + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + uint16_t const channel_mask = mtv->channel_selector().get_selected_channels (); + for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) { if ((*i)->selected()) { use_next = true; continue; } else if (use_next) { - if (!add_to_selection) { - unique_select (*i); - } else { - note_selected (*i, true, false); + if (channel_mask & (1 << (*i)->note()->channel())) { + if (!add_to_selection) { + unique_select (*i); + } else { + note_selected (*i, true, false); + } + return; } - return; } } /* use the last one */ - unique_select (*(_events.rbegin())); + if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) { + unique_select (*(_events.rbegin())); + } } void @@ -3293,30 +3389,23 @@ MidiRegionView::update_ghost_note (double x, double y) PublicEditor& editor = trackview.editor (); framepos_t const unsnapped_frame = editor.pixel_to_frame (x); + framecnt_t grid_frames; + framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames); + /* use region_frames... because we are converting a delta within the region + */ + bool success; - Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, unsnapped_frame); + double length = editor.get_grid_type_as_beats (success, unsnapped_frame); if (!success) { - grid_beats = 1; + length = 1; } - framecnt_t const grid_frames = beats_to_frames (grid_beats); - - framepos_t f; - - if (unsnapped_frame < grid_frames / 2) { - f = snap_frame_to_frame (unsnapped_frame); - } else { - /* snap to half the grid spacing behind the mouse pointer; - this makes the snapped note time more intuitive - */ - f = snap_frame_to_frame (unsnapped_frame - grid_frames / 2); - } - - double length = frames_to_beats (snap_frame_to_frame (f + grid_frames) - f); - - _ghost_note->note()->set_time (frames_to_beats (f + _region->start())); + /* note that this sets the time of the ghost note in beats relative to + the start of the source; that is how all note times are stored. + */ + _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ())); _ghost_note->note()->set_length (length); _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y)); _ghost_note->note()->set_channel (mtv->get_channel_for_add ()); @@ -3330,8 +3419,7 @@ MidiRegionView::update_ghost_note (double x, double y) void MidiRegionView::create_ghost_note (double x, double y) { - delete _ghost_note; - _ghost_note = 0; + remove_ghost_note (); boost::shared_ptr g (new NoteType); _ghost_note = new NoEventCanvasNote (*this, *_note_group, g); @@ -3440,7 +3528,7 @@ MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos) _step_edit_cursor_position = pos; if (_step_edit_cursor) { - double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos)); + double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos)); _step_edit_cursor->property_x1() = pixel; set_step_edit_cursor_width (_step_edit_cursor_width); } @@ -3460,16 +3548,15 @@ MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats) _step_edit_cursor_width = beats; if (_step_edit_cursor) { - _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (beats_to_frames (beats)); + _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats)); } } /** Called when a diskstream on our track has received some data. Update the view, if applicable. - * @param buf Data that has been recorded. - * @param w Source that this data will end up in. + * @param w Source that the data will end up in. */ void -MidiRegionView::data_recorded (boost::shared_ptr buf, boost::weak_ptr w) +MidiRegionView::data_recorded (boost::weak_ptr w) { if (!_active_notes) { /* we aren't actively being recorded to */ @@ -3483,6 +3570,9 @@ MidiRegionView::data_recorded (boost::shared_ptr buf, boost::weak_pt } MidiTimeAxisView* mtv = dynamic_cast (&trackview); + + boost::shared_ptr buf = mtv->midi_track()->get_gui_feed_buffer (); + BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0)); framepos_t back = max_framepos; @@ -3491,6 +3581,11 @@ MidiRegionView::data_recorded (boost::shared_ptr buf, boost::weak_pt Evoral::MIDIEvent const ev (*i, false); assert (ev.buffer ()); + /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is + frames from the start of the source, and so time_beats is in terms of the + source. + */ + Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ()); if (ev.type() == MIDI_CMD_NOTE_ON) { @@ -3545,7 +3640,7 @@ MidiRegionView::trim_front_ending () void MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc) { - PatchChangeDialog d (&_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY); + PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY); if (d.run () != Gtk::RESPONSE_ACCEPT) { return; } @@ -3589,3 +3684,45 @@ MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double trackview.editor().verbose_cursor()->set (text, wx, wy); trackview.editor().verbose_cursor()->show (); } + +/** @param p A session framepos. + * @param grid_frames Filled in with the number of frames that a grid interval is at p. + * @return p snapped to the grid subdivision underneath it. + */ +framepos_t +MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const +{ + PublicEditor& editor = trackview.editor (); + + bool success; + Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p); + + if (!success) { + grid_beats = 1; + } + + grid_frames = region_beats_to_region_frames (grid_beats); + + /* Hack so that we always snap to the note that we are over, instead of snapping + to the next one if we're more than halfway through the one we're over. + */ + if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) { + p -= grid_frames / 2; + } + + return snap_frame_to_frame (p); +} + +/** Called when the selection has been cleared in any MidiRegionView. + * @param rv MidiRegionView that the selection was cleared in. + */ +void +MidiRegionView::selection_cleared (MidiRegionView* rv) +{ + if (rv == this) { + return; + } + + /* Clear our selection in sympathy; but don't signal the fact */ + clear_selection (false); +}