X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=23c761178c1403ec521e513c385af74e3ba56ade;hb=4438263d4d0168581329952a995d0c5887d7f7d2;hp=a576de48fdfca2faeb99837733c35112ee22e8d1;hpb=934e827420de65504450d25f25f79a2b6df5060f;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index a576de48fd..23c761178c 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -27,31 +27,35 @@ #include +#include "midi++/midnam_patch.h" + #include "pbd/memento_command.h" #include "pbd/stateful_diff_command.h" #include "ardour/midi_model.h" -#include "ardour/midi_patch_manager.h" #include "ardour/midi_region.h" #include "ardour/midi_source.h" #include "ardour/midi_track.h" +#include "ardour/operations.h" #include "ardour/session.h" #include "evoral/Parameter.hpp" -#include "evoral/MIDIParameters.hpp" #include "evoral/MIDIEvent.hpp" #include "evoral/Control.hpp" #include "evoral/midi_util.h" #include "canvas/debug.h" +#include "canvas/text.h" #include "automation_region_view.h" #include "automation_time_axis.h" +#include "control_point.h" #include "debug.h" #include "editor.h" #include "editor_drag.h" #include "ghostregion.h" #include "gui_thread.h" +#include "item_counts.h" #include "keyboard.h" #include "midi_channel_dialog.h" #include "midi_cut_buffer.h" @@ -63,12 +67,12 @@ #include "midi_velocity_dialog.h" #include "mouse_cursors.h" #include "note_player.h" +#include "paste_context.h" #include "public_editor.h" #include "route_time_axis.h" #include "rgb_macros.h" #include "selection.h" #include "streamview.h" -#include "utils.h" #include "patch_change_dialog.h" #include "verbose_cursor.h" #include "ardour_ui.h" @@ -82,19 +86,25 @@ using namespace ARDOUR; using namespace PBD; using namespace Editing; +using namespace std; 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, - boost::shared_ptr r, double spu, Gdk::Color const & basic_color) +MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, + RouteTimeAxisView& tv, + boost::shared_ptr r, + double spu, + uint32_t basic_color) : RegionView (parent, tv, r, spu, basic_color) , _current_range_min(0) , _current_range_max(0) + , _region_relative_time_converter(r->session().tempo_map(), r->position()) + , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start()) , _active_notes(0) - , _note_group (new ArdourCanvas::Group (group)) + , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) , _ghost_note(0) , _step_edit_cursor (0) @@ -108,11 +118,12 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) + , _last_display_zoom (0) , _last_event_x (0) , _last_event_y (0) - , pre_enter_cursor (0) - , pre_press_cursor (0) - , _note_player (0) + , _grabbed_keyboard (false) + , _entered (false) + , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); _note_group->raise_to_top(); @@ -122,16 +133,25 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & connect_to_diskstream (); SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); + + PublicEditor& editor (trackview.editor()); + editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ()); } -MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, - boost::shared_ptr r, double spu, Gdk::Color& basic_color, - TimeAxisViewItem::Visibility visibility) - : RegionView (parent, tv, r, spu, basic_color, false, visibility) +MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, + RouteTimeAxisView& tv, + boost::shared_ptr r, + double spu, + uint32_t basic_color, + bool recording, + TimeAxisViewItem::Visibility visibility) + : RegionView (parent, tv, r, spu, basic_color, recording, visibility) , _current_range_min(0) , _current_range_max(0) + , _region_relative_time_converter(r->session().tempo_map(), r->position()) + , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start()) , _active_notes(0) - , _note_group (new ArdourCanvas::Group (parent)) + , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) , _ghost_note(0) , _step_edit_cursor (0) @@ -145,11 +165,12 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) + , _last_display_zoom (0) , _last_event_x (0) , _last_event_y (0) - , pre_enter_cursor (0) - , pre_press_cursor (0) - , _note_player (0) + , _grabbed_keyboard (false) + , _entered (false) + , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); _note_group->raise_to_top(); @@ -159,12 +180,15 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & connect_to_diskstream (); SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); + + PublicEditor& editor (trackview.editor()); + editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ()); } void MidiRegionView::parameter_changed (std::string const & p) { - if (p == "diplay-first-midi-bank-as-zero") { + if (p == "display-first-midi-bank-as-zero") { if (_enable_display) { redisplay_model(); } @@ -176,8 +200,10 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , RegionView (other) , _current_range_min(0) , _current_range_max(0) + , _region_relative_time_converter(other.region_relative_time_converter()) + , _source_relative_time_converter(other.source_relative_time_converter()) , _active_notes(0) - , _note_group (new ArdourCanvas::Group (get_canvas_group())) + , _note_group (new ArdourCanvas::Container (get_canvas_group())) , _note_diff_command (0) , _ghost_note(0) , _step_edit_cursor (0) @@ -191,27 +217,24 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) + , _last_display_zoom (0) , _last_event_x (0) , _last_event_y (0) - , pre_enter_cursor (0) - , pre_press_cursor (0) - , _note_player (0) + , _grabbed_keyboard (false) + , _entered (false) + , _mouse_changed_selection (false) { - Gdk::Color c; - int r,g,b,a; - - UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a); - c.set_rgb_p (r/255.0, g/255.0, b/255.0); - - init (c, false); + init (false); } MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr region) : RegionView (other, boost::shared_ptr (region)) , _current_range_min(0) , _current_range_max(0) + , _region_relative_time_converter(other.region_relative_time_converter()) + , _source_relative_time_converter(other.source_relative_time_converter()) , _active_notes(0) - , _note_group (new ArdourCanvas::Group (get_canvas_group())) + , _note_group (new ArdourCanvas::Container (get_canvas_group())) , _note_diff_command (0) , _ghost_note(0) , _step_edit_cursor (0) @@ -225,23 +248,18 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrmidi_source(0)->load_model(); + Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex()); + midi_region()->midi_source(0)->load_model(lm); } _model = midi_region()->midi_source(0)->model(); _enable_display = false; + fill_color_name = "midi frame base"; - RegionView::init (basic_color, false); - - compute_colors (basic_color); + RegionView::init (false); set_height (trackview.current_height()); @@ -291,10 +309,17 @@ MidiRegionView::init (Gdk::Color const & basic_color, bool wfd) boost::bind (&MidiRegionView::snap_changed, this), gui_context()); + trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this), + boost::bind (&MidiRegionView::mouse_mode_changed, this), + 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 ()); + + PublicEditor& editor (trackview.editor()); + editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ()); } InstrumentInfo& @@ -322,41 +347,37 @@ MidiRegionView::connect_to_diskstream () bool MidiRegionView::canvas_group_event(GdkEvent* ev) { + if (in_destructor || _recregion) { + return false; + } + + if (!trackview.editor().internal_editing()) { + // not in internal edit mode, so just act like a normal region + return RegionView::canvas_group_event (ev); + } + bool r; switch (ev->type) { case GDK_ENTER_NOTIFY: - case GDK_LEAVE_NOTIFY: _last_event_x = ev->crossing.x; _last_event_y = ev->crossing.y; - break; - case GDK_MOTION_NOTIFY: - _last_event_x = ev->motion.x; - _last_event_y = ev->motion.y; - break; - default: - break; - } - - if (ev->type == GDK_2BUTTON_PRESS) { - // cannot use double-click to exit internal mode if single-click is being used - MouseMode m = trackview.editor().current_mouse_mode(); - - if ((m != MouseObject || !Keyboard::modifier_state_contains (ev->button.state, Keyboard::insert_note_modifier())) && (m != MouseDraw)) { - return trackview.editor().toggle_internal_editing_from_double_click (ev); - } - } + enter_notify(&ev->crossing); + // set entered_regionview (among other things) + return RegionView::canvas_group_event (ev); - if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) || - (trackview.editor().current_mouse_mode() == MouseTimeFX) || - (trackview.editor().current_mouse_mode() == MouseZoom)) { - // handle non-internal-edit/non-draw modes elsewhere + case GDK_LEAVE_NOTIFY: + _last_event_x = ev->crossing.x; + _last_event_y = ev->crossing.y; + leave_notify(&ev->crossing); + // reset entered_regionview (among other things) return RegionView::canvas_group_event (ev); - } - switch (ev->type) { case GDK_SCROLL: - return scroll (&ev->scroll); + if (scroll (&ev->scroll)) { + return true; + } + break; case GDK_KEY_PRESS: return key_press (&ev->key); @@ -369,92 +390,96 @@ MidiRegionView::canvas_group_event(GdkEvent* ev) case GDK_BUTTON_RELEASE: r = button_release (&ev->button); - delete _note_player; - _note_player = 0; + _note_player.reset(); return r; - case GDK_ENTER_NOTIFY: - return enter_notify (&ev->crossing); - - case GDK_LEAVE_NOTIFY: - return leave_notify (&ev->crossing); - case GDK_MOTION_NOTIFY: + _last_event_x = ev->motion.x; + _last_event_y = ev->motion.y; return motion (&ev->motion); default: break; } - return trackview.editor().canvas_region_view_event (ev, group, this); + return RegionView::canvas_group_event (ev); } -void -MidiRegionView::remove_ghost_note () +bool +MidiRegionView::enter_notify (GdkEventCrossing* ev) { - delete _ghost_note; - _ghost_note = 0; + enter_internal(); + + _entered = true; + return false; } bool -MidiRegionView::enter_notify (GdkEventCrossing* ev) +MidiRegionView::leave_notify (GdkEventCrossing*) { - trackview.editor().MouseModeChanged.connect ( - _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context () - ); + leave_internal(); + + _entered = false; + return false; +} +void +MidiRegionView::mouse_mode_changed () +{ + // Adjust frame colour (become more transparent for internal tools) + set_frame_color(); + + if (_entered) { + if (trackview.editor().internal_editing()) { + // Switched in to internal editing mode while entered + enter_internal(); + } else { + // Switched out of internal editing mode while entered + leave_internal(); + } + } +} + +void +MidiRegionView::enter_internal() +{ if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) { - create_ghost_note (ev->x, ev->y); + // Show ghost note under pencil + create_ghost_note(_last_event_x, _last_event_y); } - if (!trackview.editor().internal_editing()) { - Keyboard::magic_widget_drop_focus(); - } else { + if (!_selection.empty()) { + // Grab keyboard for moving selected notes with arrow keys Keyboard::magic_widget_grab_focus(); - group->grab_focus(); + _grabbed_keyboard = true; } - // if current operation is non-operational in a midi region, change the cursor to so indicate - if (trackview.editor().current_mouse_mode() == MouseGain) { - Editor* editor = dynamic_cast (&trackview.editor()); - pre_enter_cursor = editor->get_canvas_cursor(); - editor->set_canvas_cursor(editor->cursors()->timebar); + // Lower frame handles below notes so they don't steal events + if (frame_handle_start) { + frame_handle_start->lower_to_bottom(); + } + if (frame_handle_end) { + frame_handle_end->lower_to_bottom(); } - - return false; } -bool -MidiRegionView::leave_notify (GdkEventCrossing*) +void +MidiRegionView::leave_internal() { - _mouse_mode_connection.disconnect (); - trackview.editor().verbose_cursor()->hide (); remove_ghost_note (); - if (pre_enter_cursor) { - Editor* editor = dynamic_cast (&trackview.editor()); - editor->set_canvas_cursor(pre_enter_cursor); + if (_grabbed_keyboard) { + Keyboard::magic_widget_drop_focus(); + _grabbed_keyboard = false; } - return false; -} - -void -MidiRegionView::mouse_mode_changed () -{ - 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 (); + // Raise frame handles above notes so they catch events + if (frame_handle_start) { + frame_handle_start->raise_to_top(); } - - if (!trackview.editor().internal_editing()) { - Keyboard::magic_widget_drop_focus(); - } else { - Keyboard::magic_widget_grab_focus(); - group->grab_focus(); + if (frame_handle_end) { + frame_handle_end->raise_to_top(); } } @@ -468,10 +493,9 @@ MidiRegionView::button_press (GdkEventButton* ev) Editor* editor = dynamic_cast (&trackview.editor()); MouseMode m = editor->current_mouse_mode(); - 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 (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { + _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil); + } if (_mouse_state != SelectTouchDragging) { @@ -482,6 +506,7 @@ MidiRegionView::button_press (GdkEventButton* ev) } _pressed_button = ev->button; + _mouse_changed_selection = false; return true; } @@ -503,10 +528,7 @@ MidiRegionView::button_release (GdkEventButton* ev) PublicEditor& editor = trackview.editor (); - if (pre_press_cursor) { - dynamic_cast(&editor)->set_canvas_cursor (pre_press_cursor, false); - pre_press_cursor = 0; - } + _press_cursor_ctx.reset(); switch (_mouse_state) { case Pressed: // Clicked @@ -515,12 +537,14 @@ MidiRegionView::button_release (GdkEventButton* ev) case MouseRange: /* no motion occured - simple click */ clear_selection (); + _mouse_changed_selection = true; break; - case MouseObject: + case MouseContent: case MouseTimeFX: { clear_selection(); + _mouse_changed_selection = true; if (Keyboard::is_insert_note_event(ev)) { @@ -530,17 +554,12 @@ MidiRegionView::button_release (GdkEventButton* ev) event_y = ev->y; group->canvas_to_item (event_x, event_y); - bool success; - Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_sample (event_x)); - - if (!success) { - beats = 1; - } + Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x)); /* 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; + beats -= Evoral::Beats::tick(); create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); } @@ -549,17 +568,12 @@ MidiRegionView::button_release (GdkEventButton* ev) } case MouseDraw: { - bool success; - Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_sample (event_x)); - - if (!success) { - beats = 1; - } + Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x)); /* 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; + beats -= Evoral::Beats::tick(); create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); @@ -584,6 +598,11 @@ MidiRegionView::button_release (GdkEventButton* ev) break; } + if(_mouse_changed_selection) { + trackview.editor().begin_reversible_selection_op (_("Mouse Selection Change")); + trackview.editor().commit_reversible_selection_op (); + } + return false; } @@ -592,18 +611,18 @@ MidiRegionView::motion (GdkEventMotion* ev) { PublicEditor& editor = trackview.editor (); - if (!_ghost_note && editor.current_mouse_mode() == MouseObject && + if (!_ghost_note && editor.current_mouse_mode() == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) && _mouse_state != AddDragging) { create_ghost_note (ev->x, ev->y); - } else if (_ghost_note && editor.current_mouse_mode() == MouseObject && + } else if (_ghost_note && editor.current_mouse_mode() == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { update_ghost_note (ev->x, ev->y); - } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) { + } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) { remove_ghost_note (); editor.verbose_cursor()->hide (); @@ -626,15 +645,18 @@ MidiRegionView::motion (GdkEventMotion* ev) MouseMode m = editor.current_mouse_mode(); - if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) { + if (m == MouseDraw || (m == MouseContent && 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) { + } else if (m == MouseContent) { editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); - clear_selection (); + if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + clear_selection (); + _mouse_changed_selection = true; + } _mouse_state = SelectRectDragging; return true; } else if (m == MouseRange) { @@ -657,9 +679,13 @@ MidiRegionView::motion (GdkEventMotion* ev) default: break; + } - return false; + /* we may be dragging some non-note object (eg. patch-change, sysex) + */ + + return editor.drags()->motion_handler ((GdkEvent *) ev, false); } @@ -712,14 +738,14 @@ MidiRegionView::key_press (GdkEventKey* ev) clear_selection(); _mouse_state = None; - } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) { + } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) { bool start = (ev->keyval == GDK_comma); bool end = (ev->keyval == GDK_period); bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier); bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); - change_note_lengths (fine, shorter, 0.0, start, end); + change_note_lengths (fine, shorter, Evoral::Beats(), start, end); return true; @@ -734,22 +760,32 @@ MidiRegionView::key_press (GdkEventKey* ev) } else if (ev->keyval == GDK_Tab) { + trackview.editor().begin_reversible_selection_op (_("Select Adjacent Note")); + if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); } else { goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); } + + trackview.editor().commit_reversible_selection_op(); + return true; } else if (ev->keyval == GDK_ISO_Left_Tab) { /* Shift-TAB generates ISO Left Tab, for some reason */ + trackview.editor().begin_reversible_selection_op (_("Select Adjacent Note")); + if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); } else { goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); } + + trackview.editor().commit_reversible_selection_op(); + return true; @@ -780,14 +816,16 @@ MidiRegionView::key_press (GdkEventKey* ev) } return true; - } else if (ev->keyval == GDK_Left && unmodified) { + } else if (ev->keyval == GDK_Left) { - nudge_notes (false); + bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); + nudge_notes (false, fine); return true; - } else if (ev->keyval == GDK_Right && unmodified) { + } else if (ev->keyval == GDK_Right) { - nudge_notes (true); + bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); + nudge_notes (true, fine); return true; } else if (ev->keyval == GDK_c && unmodified) { @@ -900,16 +938,14 @@ MidiRegionView::show_list_editor () * \param snap_t true to snap t to the grid, otherwise false. */ void -MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t) +MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bool snap_t) { if (length < 2 * DBL_EPSILON) { return; } - MidiTimeAxisView* const mtv = dynamic_cast(&trackview); - MidiStreamView* const view = mtv->midi_view(); - - const double note = view->y_to_note(y); + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + MidiStreamView* const view = mtv->midi_view(); // Start of note in frames relative to region start if (snap_t) { @@ -917,11 +953,15 @@ MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap t = snap_frame_to_grid_underneath (t, grid_frames); } - const boost::shared_ptr new_note ( - new NoteType (mtv->get_channel_for_add (), - region_frames_to_region_beats(t + _region->start()), - length, - (uint8_t)note, 0x40)); + const MidiModel::TimeType beat_time = region_frames_to_region_beats( + t + _region->start()); + + const double note = view->y_to_note(y); + const uint8_t chan = mtv->get_channel_for_add(); + const uint8_t velocity = get_velocity_for_add(beat_time); + + const boost::shared_ptr new_note( + new NoteType (chan, beat_time, length, (uint8_t)note, velocity)); if (_model->contains (new_note)) { return; @@ -929,9 +969,11 @@ MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap view->update_note_range(new_note->note()); + trackview.editor().begin_reversible_command(_("add note")); MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note")); cmd->add (new_note); _model->apply_command(*trackview.session(), cmd); + trackview.editor().commit_reversible_command(); play_midi_note (new_note); } @@ -965,8 +1007,8 @@ MidiRegionView::display_model(boost::shared_ptr model) content_connection.disconnect (); _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context()); - - clear_events (); + /* Don't signal as nobody else needs to know until selection has been altered.*/ + clear_events (false); if (_enable_display) { redisplay_model(); @@ -977,6 +1019,7 @@ void MidiRegionView::start_note_diff_command (string name) { if (!_note_diff_command) { + trackview.editor().begin_reversible_command (name); _note_diff_command = _model->new_note_diff_command (name); } } @@ -1016,7 +1059,7 @@ MidiRegionView::note_diff_add_change (NoteBase* ev, void MidiRegionView::note_diff_add_change (NoteBase* ev, MidiModel::NoteDiffCommand::Property property, - Evoral::MusicalTime val) + Evoral::Beats val) { if (_note_diff_command) { _note_diff_command->change (ev->note(), property, val); @@ -1027,6 +1070,7 @@ void MidiRegionView::apply_diff (bool as_subcommand) { bool add_or_remove; + bool commit = false; if (!_note_diff_command) { return; @@ -1043,6 +1087,7 @@ MidiRegionView::apply_diff (bool as_subcommand) _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command); } else { _model->apply_command (*trackview.session(), _note_diff_command); + commit = true; } _note_diff_command = 0; @@ -1053,6 +1098,9 @@ MidiRegionView::apply_diff (bool as_subcommand) } _marked_for_velocity.clear(); + if (commit) { + trackview.editor().commit_reversible_command (); + } } void @@ -1083,8 +1131,29 @@ MidiRegionView::find_canvas_note (boost::shared_ptr note) return 0; } +/** This version finds any canvas note matching the supplied note.*/ +NoteBase* +MidiRegionView::find_canvas_note (NoteType note) +{ + if (_optimization_iterator != _events.end()) { + ++_optimization_iterator; + } + + if (_optimization_iterator != _events.end() && (*(*_optimization_iterator)->note()) == note) { + return *_optimization_iterator; + } + + for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) { + if (*((*_optimization_iterator)->note()) == note) { + return *_optimization_iterator; + } + } + + return 0; +} + void -MidiRegionView::get_events (Events& e, Evoral::Sequence::NoteOperator op, uint8_t val, int chan_mask) +MidiRegionView::get_events (Events& e, Evoral::Sequence::NoteOperator op, uint8_t val, int chan_mask) { MidiModel::Notes notes; _model->get_notes (notes, op, val, chan_mask); @@ -1100,8 +1169,20 @@ MidiRegionView::get_events (Events& e, Evoral::Sequence::No void MidiRegionView::redisplay_model() { - // Don't redisplay the model if we're currently recording and displaying that if (_active_notes) { + // Currently recording + const framecnt_t zoom = trackview.editor().get_current_zoom(); + if (zoom != _last_display_zoom) { + /* Update resolved canvas notes to reflect changes in zoom without + touching model. Leave active notes (with length 0) alone since + they are being extended. */ + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + if ((*i)->note()->length() > 0) { + update_note(*i); + } + } + _last_display_zoom = zoom; + } return; } @@ -1118,7 +1199,7 @@ MidiRegionView::redisplay_model() MidiModel::Notes& notes (_model->notes()); _optimization_iterator = _events.begin(); - bool empty_when_starting = !_events.empty(); + bool empty_when_starting = _events.empty(); for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { @@ -1127,19 +1208,11 @@ MidiRegionView::redisplay_model() bool visible; if (note_in_region_range (note, visible)) { - - if (empty_when_starting && (cne = find_canvas_note (note)) != 0) { + + if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { cne->validate (); - - Note* cn; - Hit* ch; - - if ((cn = dynamic_cast(cne)) != 0) { - update_note (cn); - } else if ((ch = dynamic_cast(cne)) != 0) { - update_hit (ch); - } + update_note (cne); if (visible) { cne->show (); @@ -1149,22 +1222,28 @@ MidiRegionView::redisplay_model() } else { - add_note (note, visible); + cne = add_note (note, visible); } - } else { + set >::iterator it; + for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) { + if (*(*it) == *note) { + add_to_selection (cne); + } + } - if (empty_when_starting && (cne = find_canvas_note (note)) != 0) { + } else { + + if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { cne->validate (); cne->hide (); } } } - /* remove note items that are no longer valid */ - if (empty_when_starting) { + if (!empty_when_starting) { for (Events::iterator i = _events.begin(); i != _events.end(); ) { if (!(*i)->valid ()) { @@ -1192,6 +1271,7 @@ MidiRegionView::redisplay_model() _marked_for_selection.clear (); _marked_for_velocity.clear (); + _pending_note_selection.clear (); /* we may have caused _events to contain things out of order (e.g. if a note moved earlier or later). we don't generally need them in time order, but @@ -1235,11 +1315,11 @@ MidiRegionView::display_sysexes() bool have_periodic_system_messages = false; bool display_periodic_messages = true; - if (!Config->get_never_display_periodic_midi()) { + if (!ARDOUR_UI::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); + const boost::shared_ptr > mev = + boost::static_pointer_cast > (*i); if (mev) { if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { @@ -1271,10 +1351,10 @@ MidiRegionView::display_sysexes() for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) { - const boost::shared_ptr > mev = - boost::static_pointer_cast > (*i); + const boost::shared_ptr > mev = + boost::static_pointer_cast > (*i); - Evoral::MusicalTime time = (*i)->time(); + Evoral::Beats time = (*i)->time(); if (mev) { if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { @@ -1348,11 +1428,17 @@ MidiRegionView::region_resized (const PropertyChange& what_changed) RegionView::region_resized(what_changed); if (what_changed.contains (ARDOUR::Properties::position)) { + _region_relative_time_converter.set_origin_b(_region->position()); set_duration(_region->length(), 0); if (_enable_display) { redisplay_model(); } } + + if (what_changed.contains (ARDOUR::Properties::start) || + what_changed.contains (ARDOUR::Properties::position)) { + _source_relative_time_converter.set_origin_b (_region->position() - _region->start()); + } } void @@ -1379,14 +1465,12 @@ MidiRegionView::reset_width_dependent_items (double pixel_width) void MidiRegionView::set_height (double height) { - static const double FUDGE = 2.0; - const double old_height = _height; + double old_height = _height; RegionView::set_height(height); - _height = height - FUDGE; - apply_note_range(midi_stream_view()->lowest_note(), - midi_stream_view()->highest_note(), - height != old_height + FUDGE); + apply_note_range (midi_stream_view()->lowest_note(), + midi_stream_view()->highest_note(), + height != old_height); if (name_text) { name_text->raise_to_top(); @@ -1432,17 +1516,14 @@ MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force) if (Note* cnote = dynamic_cast(event)) { - const double y0 = midi_stream_view()->note_to_y(note->note()); - const double y1 = y0 + floor(midi_stream_view()->note_height()); + const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note())); + const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.); cnote->set_y0 (y0); cnote->set_y1 (y1); } else if (Hit* chit = dynamic_cast(event)) { - - const double diamond_size = update_hit (chit); - - chit->set_height (diamond_size); + update_hit (chit); } } } @@ -1450,8 +1531,6 @@ MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force) GhostRegion* MidiRegionView::add_ghost (TimeAxisView& tv) { - Note* note; - double unit_position = _region->position () / samples_per_pixel; MidiTimeAxisView* mtv = dynamic_cast(&tv); MidiGhostRegion* ghost; @@ -1466,9 +1545,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv) } for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - if ((note = dynamic_cast(*i)) != 0) { - ghost->add_note(note); - } + ghost->add_note(*i); } ghost->set_height (); @@ -1511,22 +1588,23 @@ MidiRegionView::end_write() /** Resolve an active MIDI note (while recording). */ void -MidiRegionView::resolve_note(uint8_t note, double end_time) +MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time) { if (midi_view()->note_mode() != Sustained) { return; } if (_active_notes && _active_notes[note]) { + /* Set note length so update_note() works. Note this is a local note + for recording, not from a model, so we can safely mess with it. */ + _active_notes[note]->note()->set_length( + end_time - _active_notes[note]->note()->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 - */ + /* End time is relative to the region being recorded. */ const framepos_t end_time_frames = region_beats_to_region_frames(end_time); _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames)); - _active_notes[note]->set_outline_what (0xf); + _active_notes[note]->set_outline_all (); _active_notes[note] = 0; } } @@ -1541,18 +1619,18 @@ MidiRegionView::extend_active_notes() return; } - for (unsigned i=0; i < 128; ++i) { + for (unsigned i = 0; i < 128; ++i) { if (_active_notes[i]) { - _active_notes[i]->set_x1 (trackview.editor().sample_to_pixel(_region->length())); + _active_notes[i]->set_x1( + trackview.editor().sample_to_pixel(_region->length())); } } } - void MidiRegionView::play_midi_note(boost::shared_ptr note) { - if (_no_sound_notes || !Config->get_sound_midi_notes()) { + if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) { return; } @@ -1572,26 +1650,14 @@ MidiRegionView::play_midi_note(boost::shared_ptr note) void MidiRegionView::start_playing_midi_note(boost::shared_ptr note) { - if (_no_sound_notes || !Config->get_sound_midi_notes()) { - return; - } - - RouteUI* route_ui = dynamic_cast (&trackview); - - if (!route_ui || !route_ui->midi_track()) { - return; - } - - delete _note_player; - _note_player = new NotePlayer (route_ui->midi_track ()); - _note_player->add (note); - _note_player->on (); + const std::vector< boost::shared_ptr > notes(1, note); + start_playing_midi_chord(notes); } void MidiRegionView::start_playing_midi_chord (vector > notes) { - if (_no_sound_notes || !Config->get_sound_midi_notes()) { + if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) { return; } @@ -1601,8 +1667,7 @@ MidiRegionView::start_playing_midi_chord (vector > n return; } - delete _note_player; - _note_player = new NotePlayer (route_ui->midi_track()); + _note_player = boost::shared_ptr(new NotePlayer(route_ui->midi_track())); for (vector >::iterator n = notes.begin(); n != notes.end(); ++n) { _note_player->add (*n); @@ -1624,16 +1689,28 @@ MidiRegionView::note_in_region_range (const boost::shared_ptr note, bo return !outside; } +void +MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions) +{ + Note* sus = NULL; + Hit* hit = NULL; + if ((sus = dynamic_cast(note))) { + update_sustained(sus, update_ghost_regions); + } else if ((hit = dynamic_cast(note))) { + update_hit(hit, update_ghost_regions); + } +} + /** Update a canvas note's size from its model note. * @param ev Canvas note to update. * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false. */ void -MidiRegionView::update_note (Note* ev, bool update_ghost_regions) +MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions) { boost::shared_ptr note = ev->note(); const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time())); - const double y0 = midi_stream_view()->note_to_y(note->note()); + const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note())); ev->set_x0 (x); ev->set_y0 (y0); @@ -1647,27 +1724,33 @@ MidiRegionView::update_note (Note* ev, bool update_ghost_regions) ev->set_x1 (trackview.editor().sample_to_pixel (_region->length())); } - ev->set_y1 (y0 + floor(midi_stream_view()->note_height())); + ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1)); - if (note->length() == 0) { + if (!note->length()) { if (_active_notes && note->note() < 128) { - // If this note is already active there's a stuck note, - // finish the old note rectangle - if (_active_notes[note->note()]) { - Note* const old_rect = _active_notes[note->note()]; - boost::shared_ptr old_note = old_rect->note(); + Note* const old_rect = _active_notes[note->note()]; + if (old_rect) { + /* There is an active note on this key, so we have a stuck + note. Finish the old rectangle here. */ old_rect->set_x1 (x); - old_rect->set_outline_what (0xF); + old_rect->set_outline_all (); } _active_notes[note->note()] = ev; } /* outline all but right edge */ - ev->set_outline_what (0x1 & 0x4 & 0x8); + ev->set_outline_what (ArdourCanvas::Rectangle::What ( + ArdourCanvas::Rectangle::TOP| + ArdourCanvas::Rectangle::LEFT| + ArdourCanvas::Rectangle::BOTTOM)); } else { /* outline all edges */ - ev->set_outline_what (0xF); + ev->set_outline_all (); } - + + // Update color in case velocity has changed + ev->set_fill_color(ev->base_color()); + ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected())); + if (update_ghost_regions) { for (std::vector::iterator i = ghosts.begin(); i != ghosts.end(); ++i) { MidiGhostRegion* gr = dynamic_cast (*i); @@ -1678,19 +1761,31 @@ MidiRegionView::update_note (Note* ev, bool update_ghost_regions) } } -double -MidiRegionView::update_hit (Hit* ev) +void +MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions) { boost::shared_ptr note = ev->note(); const framepos_t note_start_frames = source_beats_to_region_frames(note->time()); const double x = trackview.editor().sample_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); + const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.); + const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5; ev->set_position (ArdourCanvas::Duple (x, y)); + ev->set_height (diamond_size); + + // Update color in case velocity has changed + ev->set_fill_color(ev->base_color()); + ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected())); - return diamond_size; + if (update_ghost_regions) { + for (std::vector::iterator i = ghosts.begin(); i != ghosts.end(); ++i) { + MidiGhostRegion* gr = dynamic_cast (*i); + if (gr) { + gr->update_note (ev); + } + } + } } /** Add a MIDI note to the view (with length). @@ -1699,32 +1794,22 @@ MidiRegionView::update_hit (Hit* ev) * notes, and resolve_note should be called when the corresponding note off * event arrives, to properly display the note. */ -void +NoteBase* MidiRegionView::add_note(const boost::shared_ptr note, bool visible) { NoteBase* event = 0; - //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group(); - if (midi_view()->note_mode() == Sustained) { Note* ev_rect = new Note (*this, _note_group, note); - update_note (ev_rect); + update_sustained (ev_rect); event = ev_rect; - MidiGhostRegion* gr; - - for (std::vector::iterator g = ghosts.begin(); g != ghosts.end(); ++g) { - if ((gr = dynamic_cast(*g)) != 0) { - gr->add_note(ev_rect); - } - } - } else if (midi_view()->note_mode() == Percussive) { - const double diamond_size = midi_stream_view()->note_height() / 2.0; + const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.); Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); @@ -1737,6 +1822,14 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) } if (event) { + MidiGhostRegion* gr; + + for (std::vector::iterator g = ghosts.begin(); g != ghosts.end(); ++g) { + if ((gr = dynamic_cast(*g)) != 0) { + gr->add_note(event); + } + } + if (_marked_for_selection.find(note) != _marked_for_selection.end()) { note_selected(event, true); } @@ -1759,11 +1852,12 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) MidiStreamView* const view = mtv->midi_view(); view->update_note_range (note->note()); + return event; } void MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity, - Evoral::MusicalTime pos, Evoral::MusicalTime len) + Evoral::Beats pos, Evoral::Beats len) { boost::shared_ptr new_note (new NoteType (channel, pos, len, number, velocity)); @@ -1792,7 +1886,7 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity } void -MidiRegionView::step_sustain (Evoral::MusicalTime beats) +MidiRegionView::step_sustain (Evoral::Beats beats) { change_note_lengths (false, false, beats, false, true); } @@ -1845,13 +1939,13 @@ MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p) /// Return true iff @p pc applies to the given time on the given channel. static bool -patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, double time, uint8_t channel) +patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel) { return pc->time() <= time && pc->channel() == channel; } void -MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const +MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const { // The earliest event not before time MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time); @@ -1864,42 +1958,43 @@ MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::Patc } if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) { - key.bank_number = (*i)->bank(); - key.program_number = (*i)->program (); + key.set_bank((*i)->bank()); + key.set_program((*i)->program ()); } else { - key.bank_number = key.program_number = 0; - } - - if (!key.is_sane()) { - error << string_compose(_("insane MIDI patch key %1:%2"), - key.bank_number, key.program_number) << endmsg; + key.set_bank(0); + key.set_program(0); } } void MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch) { - MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); + string name = _("alter patch change"); + trackview.editor().begin_reversible_command (name); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); - if (pc.patch()->program() != new_patch.program_number) { - c->change_program (pc.patch (), new_patch.program_number); + if (pc.patch()->program() != new_patch.program()) { + c->change_program (pc.patch (), new_patch.program()); } - int const new_bank = new_patch.bank_number; + int const new_bank = new_patch.bank(); if (pc.patch()->bank() != new_bank) { c->change_bank (pc.patch (), new_bank); } _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); } void -MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange & new_change) +MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange & new_change) { - MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); + string name = _("alter patch change"); + trackview.editor().begin_reversible_command (name); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); if (old_change->time() != new_change.time()) { c->change_time (old_change, new_change.time()); @@ -1918,6 +2013,7 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const } _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -1929,13 +2025,15 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const * MidiTimeAxisView::get_channel_for_add()) */ void -MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange const & patch) +MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange const & patch) { MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + string name = _("add patch change"); - MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change")); + trackview.editor().begin_reversible_command (name); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); c->add (MidiModel::PatchChangePtr ( - new Evoral::PatchChange ( + new Evoral::PatchChange ( absolute_frames_to_source_beats (_region->position() + t), mtv->get_channel_for_add(), patch.program(), patch.bank() ) @@ -1943,17 +2041,20 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChangeapply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); } void -MidiRegionView::move_patch_change (PatchChange& pc, Evoral::MusicalTime t) +MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t) { + trackview.editor().begin_reversible_command (_("move patch change")); MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change")); c->change_time (pc.patch (), t); _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -1962,56 +2063,26 @@ MidiRegionView::move_patch_change (PatchChange& pc, Evoral::MusicalTime t) void MidiRegionView::delete_patch_change (PatchChange* pc) { + trackview.editor().begin_reversible_command (_("delete patch change")); MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change")); c->remove (pc->patch ()); _model->apply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); } void -MidiRegionView::previous_patch (PatchChange& patch) +MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta) { - if (patch.patch()->program() < 127) { - MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); - key.program_number++; - change_patch_change (patch, key); - } -} - -void -MidiRegionView::next_patch (PatchChange& patch) -{ - if (patch.patch()->program() > 0) { - MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); - key.program_number--; - change_patch_change (patch, key); - } -} - -void -MidiRegionView::next_bank (PatchChange& patch) -{ - if (patch.patch()->program() < 127) { - MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); - if (key.bank_number > 0) { - key.bank_number--; - change_patch_change (patch, key); - } - } -} - -void -MidiRegionView::previous_bank (PatchChange& patch) -{ - if (patch.patch()->program() > 0) { - MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); - if (key.bank_number < 127) { - key.bank_number++; - change_patch_change (patch, key); - } + MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch()); + if (bank) { + key.set_bank(key.bank() + delta); + } else { + key.set_program(key.program() + delta); } + change_patch_change(patch, key); } void @@ -2072,6 +2143,12 @@ MidiRegionView::clear_selection_except (NoteBase* ev, bool signal) } } + if (!ev && _entered) { + // Clearing selection entirely, ungrab keyboard + Keyboard::magic_widget_drop_focus(); + _grabbed_keyboard = false; + } + /* this does not change the status of this regionview w.r.t the editor selection. */ @@ -2084,6 +2161,8 @@ MidiRegionView::clear_selection_except (NoteBase* ev, bool signal) void MidiRegionView::unique_select(NoteBase* ev) { + const bool selection_was_empty = _selection.empty(); + clear_selection_except (ev); /* don't bother with checking to see if we should remove this @@ -2094,6 +2173,11 @@ MidiRegionView::unique_select(NoteBase* ev) if (!ev->selected()) { add_to_selection (ev); + if (selection_was_empty && _entered) { + // Grab keyboard for moving notes with arrow keys + Keyboard::magic_widget_grab_focus(); + _grabbed_keyboard = true; + } } } @@ -2132,6 +2216,24 @@ MidiRegionView::invert_selection () } } +/** Used for selection undo/redo. + The requested notes most likely won't exist in the view until the next model redisplay. +*/ +void +MidiRegionView::select_notes (list > notes) +{ + NoteBase* cne; + list >::iterator n; + + for (n = notes.begin(); n != notes.end(); ++n) { + if ((cne = find_canvas_note(*(*n))) != 0) { + add_to_selection (cne); + } else { + _pending_note_selection.insert(*n); + } + } +} + void MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend) { @@ -2248,8 +2350,8 @@ MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend) } else { /* find end of latest note selected, select all between that and the start of "ev" */ - Evoral::MusicalTime earliest = Evoral::MaxMusicalTime; - Evoral::MusicalTime latest = 0; + Evoral::Beats earliest = Evoral::MaxBeats; + Evoral::Beats latest = Evoral::Beats(); for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { if ((*i)->note()->end_time() > latest) { @@ -2288,8 +2390,18 @@ MidiRegionView::note_deselected(NoteBase* ev) } void -MidiRegionView::update_drag_selection(double x0, double x1, double y0, double y1, bool extend) +MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend) { + PublicEditor& editor = trackview.editor(); + + // Convert to local coordinates + const framepos_t p = _region->position(); + const double y = midi_view()->y_position(); + const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p)); + const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p)); + const double y0 = max(0.0, gy0 - y); + const double y1 = max(0.0, gy1 - y); + // 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. @@ -2305,6 +2417,24 @@ MidiRegionView::update_drag_selection(double x0, double x1, double y0, double y1 remove_from_selection (*i); } } + + typedef RouteTimeAxisView::AutomationTracks ATracks; + typedef std::list Selectables; + + /* Add control points to selection. */ + const ATracks& atracks = midi_view()->automation_tracks(); + Selectables selectables; + editor.get_selection().clear_points(); + for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) { + a->second->get_selectables(start, end, gy0, gy1, selectables); + for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) { + ControlPoint* cp = dynamic_cast(*s); + if (cp) { + editor.get_selection().add(cp); + } + } + a->second->set_selected_points(editor.get_selection().points); + } } void @@ -2337,6 +2467,11 @@ MidiRegionView::remove_from_selection (NoteBase* ev) if (i != _selection.end()) { _selection.erase (i); + if (_selection.empty() && _grabbed_keyboard) { + // Ungrab keyboard + Keyboard::magic_widget_drop_focus(); + _grabbed_keyboard = false; + } } ev->set_selected (false); @@ -2351,18 +2486,19 @@ MidiRegionView::remove_from_selection (NoteBase* ev) void MidiRegionView::add_to_selection (NoteBase* ev) { - bool add_mrv_selection = false; - - if (_selection.empty()) { - add_mrv_selection = true; - } + const bool selection_was_empty = _selection.empty(); if (_selection.insert (ev).second) { ev->set_selected (true); start_playing_midi_note ((ev)->note()); + if (selection_was_empty && _entered) { + // Grab keyboard for moving notes with arrow keys + Keyboard::magic_widget_grab_focus(); + _grabbed_keyboard = true; + } } - if (add_mrv_selection) { + if (selection_was_empty) { PublicEditor& editor (trackview.editor()); editor.get_selection().add (this); } @@ -2373,7 +2509,7 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy) { typedef vector > PossibleChord; PossibleChord to_play; - Evoral::MusicalTime earliest = Evoral::MaxMusicalTime; + Evoral::Beats earliest = Evoral::MaxBeats; for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { if ((*i)->note()->time() < earliest) { @@ -2382,13 +2518,13 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy) } for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) { + if ((*i)->note()->time() == earliest) { to_play.push_back ((*i)->note()); } (*i)->move_event(dx, dy); } - if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) { + if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) { if (to_play.size() > 1) { @@ -2446,7 +2582,7 @@ MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt; - Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames); + Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames); if (new_time < 0) { continue; @@ -2509,7 +2645,7 @@ MidiRegionView::get_end_position_pixels() } framepos_t -MidiRegionView::source_beats_to_absolute_frames(double beats) const +MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const { /* the time converter will return the frame corresponding to `beats' relative to the start of the source. The start of the source @@ -2519,7 +2655,7 @@ MidiRegionView::source_beats_to_absolute_frames(double beats) const return source_start + _source_relative_time_converter.to (beats); } -double +Evoral::Beats MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const { /* the `frames' argument needs to be converted into a frame count @@ -2531,12 +2667,12 @@ MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const } framepos_t -MidiRegionView::region_beats_to_region_frames(double beats) const +MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const { return _region_relative_time_converter.to(beats); } -double +Evoral::Beats MidiRegionView::region_frames_to_region_beats(framepos_t frames) const { return _region_relative_time_converter.from(frames); @@ -2561,7 +2697,7 @@ MidiRegionView::begin_resizing (bool /*at_front*/) // calculate the colors: get the color settings uint32_t fill_color = UINT_RGBA_CHANGE_A( - ARDOUR_UI::config()->get_canvasvar_MidiNoteSelected(), + ARDOUR_UI::config()->color ("midi note selected"), 128); // make the resize preview notes more transparent and bright @@ -2574,7 +2710,7 @@ MidiRegionView::begin_resizing (bool /*at_front*/) 0.85)); resize_rect->set_outline_color (NoteBase::calculate_outline ( - ARDOUR_UI::config()->get_canvasvar_MidiNoteSelected())); + ARDOUR_UI::config()->color ("midi note selected"))); resize_data->resize_rect = resize_rect; _resize_data.push_back(resize_data); @@ -2615,6 +2751,15 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ } } + if (current_x < 0) { + // This works even with snapping because RegionView::snap_frame_to_frame() + // snaps forward if the snapped sample is before the beginning of the region + current_x = 0; + } + if (current_x > trackview.editor().sample_to_pixel(_region->length())) { + current_x = trackview.editor().sample_to_pixel(_region->length()); + } + if (at_front) { resize_rect->set_x0 (snap_to_pixel(current_x)); resize_rect->set_x1 (canvas_note->x1()); @@ -2624,30 +2769,25 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ } if (!cursor_set) { - double beats; - - beats = snap_pixel_to_sample (current_x); - beats = region_frames_to_region_beats (beats); - - double len; + const double snapped_x = snap_pixel_to_sample (current_x); + Evoral::Beats beats = region_frames_to_region_beats (snapped_x); + Evoral::Beats len = Evoral::Beats(); if (at_front) { if (beats < canvas_note->note()->end_time()) { len = canvas_note->note()->time() - beats; len += canvas_note->note()->length(); - } else { - len = 0; } } else { if (beats >= canvas_note->note()->time()) { len = beats - canvas_note->note()->time(); - } else { - len = 0; } } + len = std::max(Evoral::Beats(1 / 512.0), len); + char buf[16]; - snprintf (buf, sizeof (buf), "%.3g beats", len); + snprintf (buf, sizeof (buf), "%.3g beats", len.to_double()); show_verbose_cursor (buf, 0, 0); cursor_set = true; @@ -2689,31 +2829,34 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ } } + if (current_x < 0) { + current_x = 0; + } + if (current_x > trackview.editor().sample_to_pixel(_region->length())) { + current_x = trackview.editor().sample_to_pixel(_region->length()); + } + /* Convert that to a frame within the source */ current_x = snap_pixel_to_sample (current_x) + _region->start (); /* and then to beats */ - current_x = region_frames_to_region_beats (current_x); + const Evoral::Beats x_beats = 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); + if (at_front && x_beats < canvas_note->note()->end_time()) { + note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats); - double len = canvas_note->note()->time() - current_x; + Evoral::Beats len = canvas_note->note()->time() - x_beats; len += canvas_note->note()->length(); - if (len > 0) { - /* XXX convert to beats */ + if (!!len) { note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); } } if (!at_front) { - double len = current_x - canvas_note->note()->time(); - - if (len > 0) { - /* XXX convert to beats */ - note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); - } + const Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0), + x_beats - canvas_note->note()->time()); + note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); } delete resize_rect; @@ -2768,12 +2911,12 @@ MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative) } void -MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta) +MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta) { bool change_start = false; bool change_length = false; - Evoral::MusicalTime new_start = 0; - Evoral::MusicalTime new_length = 0; + Evoral::Beats new_start; + Evoral::Beats new_length; /* NOTE: the semantics of the two delta arguments are slightly subtle: @@ -2784,11 +2927,11 @@ MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evo if negative - move the end of the note earlier in time (shortening it) */ - if (front_delta) { + if (!!front_delta) { if (front_delta < 0) { if (event->note()->time() < -front_delta) { - new_start = 0; + new_start = Evoral::Beats(); } else { new_start = event->note()->time() + front_delta; // moves earlier } @@ -2803,7 +2946,7 @@ MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evo } else { - Evoral::MusicalTime new_pos = event->note()->time() + front_delta; + Evoral::Beats new_pos = event->note()->time() + front_delta; if (new_pos < event->note()->end_time()) { new_start = event->note()->time() + front_delta; @@ -2816,7 +2959,7 @@ MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evo } - if (end_delta) { + if (!!end_delta) { bool can_change = true; if (end_delta < 0) { if (event->note()->length() < -end_delta) { @@ -2862,14 +3005,14 @@ MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative) } void -MidiRegionView::change_note_time (NoteBase* event, Evoral::MusicalTime delta, bool relative) +MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative) { - Evoral::MusicalTime new_time; + Evoral::Beats new_time; if (relative) { if (delta < 0.0) { if (event->note()->time() < -delta) { - new_time = 0; + new_time = Evoral::Beats(); } else { new_time = event->note()->time() + delta; } @@ -2884,7 +3027,7 @@ MidiRegionView::change_note_time (NoteBase* event, Evoral::MusicalTime delta, bo } void -MidiRegionView::change_note_length (NoteBase* event, Evoral::MusicalTime t) +MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t) { note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t); } @@ -2996,20 +3139,14 @@ MidiRegionView::transpose (bool up, bool fine, bool allow_smush) } void -MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end) +MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end) { - if (delta == 0.0) { + if (!delta) { if (fine) { - delta = 1.0/128.0; + delta = Evoral::Beats(1.0/128.0); } else { /* grab the current grid distance */ - bool success; - delta = trackview.editor().get_grid_type_as_beats (success, _region->position()); - if (!success) { - /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */ - error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg; - return; - } + delta = get_grid_beats(_region->position()); } } @@ -3025,7 +3162,9 @@ MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTim /* note the negation of the delta for start */ - trim_note (*i, (start ? -delta : 0), (end ? delta : 0)); + trim_note (*i, + (start ? -delta : Evoral::Beats()), + (end ? delta : Evoral::Beats())); i = next; } @@ -3034,7 +3173,7 @@ MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTim } void -MidiRegionView::nudge_notes (bool forward) +MidiRegionView::nudge_notes (bool forward, bool fine) { if (_selection.empty()) { return; @@ -3045,15 +3184,21 @@ MidiRegionView::nudge_notes (bool forward) into a vector and sort before using the first one. */ - framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time()); - framepos_t unused; - framecnt_t distance; + const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time()); + Evoral::Beats delta; - if (trackview.editor().snap_mode() == Editing::SnapOff) { + if (!fine) { + + /* non-fine, move by 1 bar regardless of snap */ + delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar()); + + } else if (trackview.editor().snap_mode() == Editing::SnapOff) { /* grid is off - use nudge distance */ - distance = trackview.editor().get_nudge_distance (ref_point, unused); + framepos_t unused; + const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused); + delta = region_frames_to_region_beats (fabs ((double)distance)); } else { @@ -3072,16 +3217,15 @@ MidiRegionView::nudge_notes (bool forward) next_pos -= 1; } - trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false); - distance = ref_point - next_pos; + trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false); + const framecnt_t distance = ref_point - next_pos; + delta = region_frames_to_region_beats (fabs ((double)distance)); } - if (distance == 0) { + if (!delta) { return; } - Evoral::MusicalTime delta = region_frames_to_region_beats (fabs ((double)distance)); - if (!forward) { delta = -delta; } @@ -3115,13 +3259,13 @@ MidiRegionView::note_entered(NoteBase* ev) { Editor* editor = dynamic_cast(&trackview.editor()); - pre_enter_cursor = editor->get_canvas_cursor (); - if (_mouse_state == SelectTouchDragging) { note_selected (ev, true); + } else if (editor->current_mouse_mode() == MouseContent) { + show_verbose_cursor (ev->note ()); + } else if (editor->current_mouse_mode() == MouseDraw) { + show_verbose_cursor (ev->note ()); } - - show_verbose_cursor (ev->note ()); } void @@ -3134,11 +3278,6 @@ MidiRegionView::note_left (NoteBase*) } editor->verbose_cursor()->hide (); - - if (pre_enter_cursor) { - editor->set_canvas_cursor (pre_enter_cursor); - pre_enter_cursor = 0; - } } void @@ -3187,43 +3326,33 @@ MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, boo { Editor* editor = dynamic_cast(&trackview.editor()); Editing::MouseMode mm = editor->current_mouse_mode(); - bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw); - - if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) { - editor->set_canvas_cursor (editor->cursors()->left_side_trim); - } 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); + bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw); + + Editor::EnterContext* ctx = editor->get_enter_context(NoteItem); + if (can_set_cursor && ctx) { + if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) { + ctx->cursor_ctx->change(editor->cursors()->left_side_trim); + } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) { + ctx->cursor_ctx->change(editor->cursors()->right_side_trim); + } else { + ctx->cursor_ctx->change(editor->cursors()->grabber_note); } } } -void -MidiRegionView::set_frame_color() +uint32_t +MidiRegionView::get_fill_color() const { - uint32_t f; - - TimeAxisViewItem::set_frame_color (); - - if (!frame) { - return; - } - + const std::string mod_name = (_dragging ? "dragging region" : + trackview.editor().internal_editing() ? "editable region" : + "midi frame base"); if (_selected) { - f = ARDOUR_UI::config()->get_canvasvar_SelectedFrameBase(); - } else if (high_enough_for_name) { - f= ARDOUR_UI::config()->get_canvasvar_MidiFrameBase(); - } else { - f = fill_color; + return ARDOUR_UI::config()->color_mod ("selected region base", mod_name); + } else if ((!ARDOUR_UI::config()->get_show_name_highlight() || high_enough_for_name) && + !ARDOUR_UI::config()->get_color_regions_using_track_color()) { + return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name); } - - if (!rect_visible) { - f = UINT_RGBA_CHANGE_A (f, 0); - } - - frame->set_fill_color (f); + return ARDOUR_UI::config()->color_mod (fill_color, mod_name); } void @@ -3309,35 +3438,51 @@ MidiRegionView::selection_as_cut_buffer () const return cb; } +/** This method handles undo */ +bool +MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx) +{ + // Paste notes, if available + MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes()); + if (m != selection.midi_notes.end()) { + ctx.counts.increase_n_notes(); + paste_internal(pos, ctx.count, ctx.times, **m); + } + + // Paste control points to automation children, if available + typedef RouteTimeAxisView::AutomationTracks ATracks; + const ATracks& atracks = midi_view()->automation_tracks(); + for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) { + a->second->paste(pos, selection, ctx); + } + + return true; +} + /** This method handles undo */ void -MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) +MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb) { if (mcb.empty()) { return; } - DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times)); - - trackview.session()->begin_reversible_command (_("paste")); - start_note_diff_command (_("paste")); - Evoral::MusicalTime beat_delta; - Evoral::MusicalTime paste_pos_beats; - Evoral::MusicalTime duration; - Evoral::MusicalTime end_point = 0; - - duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time(); - paste_pos_beats = absolute_frames_to_source_beats (pos); - beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats; - paste_pos_beats = 0; - - DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n", - (*mcb.notes().begin())->time(), - (*mcb.notes().rbegin())->end_time(), + const Evoral::Beats snap_beats = get_grid_beats(pos); + const Evoral::Beats first_time = (*mcb.notes().begin())->time(); + const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time(); + const Evoral::Beats duration = last_time - first_time; + const Evoral::Beats snap_duration = duration.snap_to(snap_beats); + const Evoral::Beats paste_offset = snap_duration * paste_count; + const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset; + Evoral::Beats end_point = Evoral::Beats(); + + DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n", + first_time, + last_time, duration, pos, _region->position(), - paste_pos_beats, beat_delta)); + pos_beats)); clear_selection (); @@ -3346,15 +3491,13 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) { boost::shared_ptr copied_note (new NoteType (*((*i).get()))); - copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta); + copied_note->set_time (pos_beats + copied_note->time() - first_time); /* make all newly added notes selected */ note_diff_add_note (copied_note, true); end_point = copied_note->end_time(); } - - paste_pos_beats += duration; } /* if we pasted past the current end of the region, extend the region */ @@ -3372,8 +3515,6 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) } apply_diff (true); - - trackview.session()->commit_reversible_command (); } struct EventNoteTimeEarlyFirstComparator { @@ -3493,6 +3634,8 @@ MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_s void MidiRegionView::update_ghost_note (double x, double y) { + x = std::max(0.0, x); + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); _last_ghost_x = x; @@ -3506,23 +3649,17 @@ MidiRegionView::update_ghost_note (double x, double y) 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; - double length = editor.get_grid_type_as_beats (success, unsnapped_frame); + /* calculate time in beats relative to start of source */ + const Evoral::Beats length = get_grid_beats(unsnapped_frame); + const Evoral::Beats time = std::max( + Evoral::Beats(), + absolute_frames_to_source_beats (f + _region->position ())); - if (!success) { - length = 1; - } - - /* 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_time (time); _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 ()); + _ghost_note->note()->set_velocity (get_velocity_for_add (time)); /* the ghost note does not appear in ghost regions, so pass false in here */ update_note (_ghost_note, false); @@ -3536,18 +3673,26 @@ MidiRegionView::create_ghost_note (double x, double y) remove_ghost_note (); boost::shared_ptr g (new NoteType); - _ghost_note = new Note (*this, _note_group, g); + if (midi_view()->note_mode() == Sustained) { + _ghost_note = new Note (*this, _note_group, g); + } else { + _ghost_note = new Hit (*this, _note_group, 10, g); + } _ghost_note->set_ignore_events (true); _ghost_note->set_outline_color (0x000000aa); update_ghost_note (x, y); _ghost_note->show (); - _last_ghost_x = x; - _last_ghost_y = y; - show_verbose_cursor (_ghost_note->note ()); } +void +MidiRegionView::remove_ghost_note () +{ + delete _ghost_note; + _ghost_note = 0; +} + void MidiRegionView::snap_changed () { @@ -3567,6 +3712,8 @@ MidiRegionView::drop_down_keys () void MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y) { + /* XXX: This is dead code. What was it for? */ + double note = midi_stream_view()->y_to_note(y); Events e; MidiTimeAxisView* const mtv = dynamic_cast(&trackview); @@ -3574,9 +3721,9 @@ MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, doub uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask(); if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { - get_events (e, Evoral::Sequence::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask); + get_events (e, Evoral::Sequence::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask); } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { - get_events (e, Evoral::Sequence::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask); + get_events (e, Evoral::Sequence::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask); } else { return; } @@ -3621,10 +3768,10 @@ MidiRegionView::enable_display (bool yn) } void -MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos) +MidiRegionView::show_step_edit_cursor (Evoral::Beats pos) { if (_step_edit_cursor == 0) { - ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); + ArdourCanvas::Item* const group = get_canvas_group(); _step_edit_cursor = new ArdourCanvas::Rectangle (group); _step_edit_cursor->set_y0 (0); @@ -3638,7 +3785,7 @@ MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos) } void -MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos) +MidiRegionView::move_step_edit_cursor (Evoral::Beats pos) { _step_edit_cursor_position = pos; @@ -3658,7 +3805,7 @@ MidiRegionView::hide_step_edit_cursor () } void -MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats) +MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats) { _step_edit_cursor_width = beats; @@ -3688,8 +3835,6 @@ MidiRegionView::data_recorded (boost::weak_ptr w) 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; for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) { @@ -3703,16 +3848,13 @@ MidiRegionView::data_recorded (boost::weak_ptr w) } } - /* 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 ()); + /* convert from session frames to source beats */ + Evoral::Beats const time_beats = _source_relative_time_converter.from( + ev.time() - src->timeline_position() + _region->start()); if (ev.type() == MIDI_CMD_NOTE_ON) { boost::shared_ptr note ( - new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())); + new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity())); add_note (note, true); @@ -3739,7 +3881,7 @@ MidiRegionView::trim_front_starting () /* Reparent the note group to the region view's parent, so that it doesn't change when the region view is trimmed. */ - _temporary_note_group = new ArdourCanvas::Group (group->parent ()); + _temporary_note_group = new ArdourCanvas::Container (group->parent ()); _temporary_note_group->move (group->position ()); _note_group->reparent (_temporary_note_group); } @@ -3805,8 +3947,8 @@ MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const get_patch_key_at(n->time(), n->channel(), patch_key); name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")), n->channel(), - patch_key.bank_number, - patch_key.program_number, + patch_key.bank(), + patch_key.program(), n->note()); } } @@ -3824,28 +3966,36 @@ MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const void MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const { - double wx, wy; - - trackview.editor().verbose_cursor()->set_text (text); - trackview.editor().get_pointer_position (wx, wy); - - wx += xoffset; - wy += yoffset; + trackview.editor().verbose_cursor()->set (text); + trackview.editor().verbose_cursor()->show (); + trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset)); +} - /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */ +uint8_t +MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const +{ + if (_model->notes().empty()) { + return 0x40; // No notes, use default + } - boost::optional bbo = trackview.editor().verbose_cursor()->item().bounding_box(); + MidiModel::Notes::const_iterator m = _model->note_lower_bound(time); + if (m == _model->notes().begin()) { + // Before the start, use the velocity of the first note + return (*m)->velocity(); + } else if (m == _model->notes().end()) { + // Past the end, use the velocity of the last note + --m; + return (*m)->velocity(); + } - assert (bbo); - - ArdourCanvas::Rect bb = bbo.get(); + // Interpolate velocity of surrounding notes + MidiModel::Notes::const_iterator n = m; + --n; - if ((wy + bb.y1 - bb.y0) > trackview.editor().visible_canvas_height()) { - wy -= (bb.y1 - bb.y0) + 2 * yoffset; - } + const double frac = ((time - (*n)->time()).to_double() / + ((*m)->time() - (*n)->time()).to_double()); - trackview.editor().verbose_cursor()->set_position (wx, wy); - trackview.editor().verbose_cursor()->show (); + return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity())); } /** @param p A session framepos. @@ -3857,13 +4007,8 @@ MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_fr { PublicEditor& editor = trackview.editor (); - bool success; - Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p); + const Evoral::Beats grid_beats = get_grid_beats(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 @@ -3893,8 +4038,7 @@ MidiRegionView::selection_cleared (MidiRegionView* rv) void MidiRegionView::note_button_release () { - delete _note_player; - _note_player = 0; + _note_player.reset(); } ChannelMode @@ -3911,3 +4055,15 @@ MidiRegionView::get_selected_channels () const return rtav->midi_track()->get_playback_channel_mask(); } + +Evoral::Beats +MidiRegionView::get_grid_beats(framepos_t pos) const +{ + PublicEditor& editor = trackview.editor(); + bool success = false; + Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos); + if (!success) { + beats = Evoral::Beats(1); + } + return beats; +}