X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=c3d8a1ddd56d951403991733ef202374020c6692;hb=811677c543562f8df9e332f635b982bc12b1daca;hp=acc6aafdeb2dff5246ce9ccb70e4850e247f4c55;hpb=f208593249c7bc1f139809aa32c8aa6320782af0;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index acc6aafdeb..0d44a83e5e 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/pixbuf.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) @@ -110,10 +120,14 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , _grabbed_keyboard (false) + , _entered (false) , pre_enter_cursor (0) , pre_press_cursor (0) + , pre_note_enter_cursor (0) , _note_player (0) { + CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); _note_group->raise_to_top(); PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); @@ -123,14 +137,19 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); } -MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, - boost::shared_ptr r, double spu, Gdk::Color& basic_color, - TimeAxisViewItem::Visibility visibility) +MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, + RouteTimeAxisView& tv, + boost::shared_ptr r, + double spu, + uint32_t basic_color, + TimeAxisViewItem::Visibility visibility) : RegionView (parent, tv, r, spu, basic_color, false, 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 (parent)) , _note_diff_command (0) , _ghost_note(0) , _step_edit_cursor (0) @@ -146,11 +165,16 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , _grabbed_keyboard (false) + , _entered (false) , pre_enter_cursor (0) , pre_press_cursor (0) + , pre_note_enter_cursor (0) , _note_player (0) { + CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); _note_group->raise_to_top(); + PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); connect_to_diskstream (); @@ -161,7 +185,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & 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(); } @@ -173,8 +197,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) @@ -190,25 +216,24 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , _grabbed_keyboard (false) + , _entered (false) , pre_enter_cursor (0) , pre_press_cursor (0) + , pre_note_enter_cursor (0) , _note_player (0) { - 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) @@ -224,21 +249,18 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrmidi_source(0)->model(); _enable_display = false; - RegionView::init (basic_color, false); - - compute_colors (basic_color); + RegionView::init (false); set_height (trackview.current_height()); @@ -276,8 +296,6 @@ MidiRegionView::init (Gdk::Color const & basic_color, bool wfd) reset_width_dependent_items (_pixel_width); group->raise_to_top(); - group->Event.connect (sigc::mem_fun (this, &MidiRegionView::canvas_event)); - midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this), boost::bind (&MidiRegionView::midi_channel_mode_changed, this), @@ -319,43 +337,49 @@ MidiRegionView::connect_to_diskstream () } bool -MidiRegionView::canvas_event(GdkEvent* ev) +MidiRegionView::canvas_group_event(GdkEvent* ev) { + if (in_destructor) { + 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); + } + + const MouseMode m = trackview.editor().current_mouse_mode(); bool r; switch (ev->type) { case GDK_ENTER_NOTIFY: + _last_event_x = ev->crossing.x; + _last_event_y = ev->crossing.y; + enter_notify(&ev->crossing); + // set entered_regionview (among other things) + return RegionView::canvas_group_event (ev); + 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; - } + leave_notify(&ev->crossing); + // reset entered_regionview (among other things) + return RegionView::canvas_group_event (ev); - if (ev->type == GDK_2BUTTON_PRESS) { + case 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)) { + if ((m != MouseDraw) && + (m != MouseObject || + !Keyboard::modifier_state_contains (ev->button.state, Keyboard::insert_note_modifier()))) { return trackview.editor().toggle_internal_editing_from_double_click (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-draw modes elsewhere - return false; - } + break; - 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); @@ -372,27 +396,16 @@ MidiRegionView::canvas_event(GdkEvent* ev) _note_player = 0; 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 false; -} - -void -MidiRegionView::remove_ghost_note () -{ - delete _ghost_note; - _ghost_note = 0; + return RegionView::canvas_group_event (ev); } bool @@ -402,24 +415,9 @@ MidiRegionView::enter_notify (GdkEventCrossing* ev) _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context () ); - 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(); - } - - // 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); - } + enter_internal(); + _entered = true; return false; } @@ -428,32 +426,48 @@ MidiRegionView::leave_notify (GdkEventCrossing*) { _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); - } + leave_internal(); + _entered = 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); + if (trackview.editor().internal_editing()) { + // Switched in to internal editing mode while entered + enter_internal(); } else { - remove_ghost_note (); - trackview.editor().verbose_cursor()->hide (); + // Switched out of internal editing mode while entered + leave_internal(); } +} - if (!trackview.editor().internal_editing()) { - Keyboard::magic_widget_drop_focus(); - } else { +void +MidiRegionView::enter_internal() +{ + if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) { + // Show ghost note under pencil + create_ghost_note(_last_event_x, _last_event_y); + } + + if (!_selection.empty()) { + // Grab keyboard for moving selected notes with arrow keys Keyboard::magic_widget_grab_focus(); - group->grab_focus(); + _grabbed_keyboard = true; + } +} + +void +MidiRegionView::leave_internal() +{ + trackview.editor().verbose_cursor()->hide (); + remove_ghost_note (); + + if (_grabbed_keyboard) { + Keyboard::magic_widget_drop_focus(); + _grabbed_keyboard = false; } } @@ -470,7 +484,7 @@ MidiRegionView::button_press (GdkEventButton* ev) 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) { @@ -529,38 +543,28 @@ 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_frame (event_x)); - - if (!success) { - beats = 1; - } + Evoral::MusicalTime 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::MusicalTime::tick(); - create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true); + create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); } break; } case MouseDraw: { - bool success; - Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x)); - - if (!success) { - beats = 1; - } + Evoral::MusicalTime 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::MusicalTime::tick(); - create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true); + create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); break; } @@ -626,7 +630,6 @@ 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()))) { - editor.drags()->set (new NoteCreateDrag (dynamic_cast (&editor), group, this), (GdkEvent *) ev); _mouse_state = AddDragging; remove_ghost_note (); @@ -634,7 +637,9 @@ MidiRegionView::motion (GdkEventMotion* ev) return true; } else if (m == MouseObject) { 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_state = SelectRectDragging; return true; } else if (m == MouseRange) { @@ -657,9 +662,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); } @@ -719,7 +728,7 @@ MidiRegionView::key_press (GdkEventKey* ev) bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier); bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); - change_note_lengths (fine, shorter, 0.0, start, end); + change_note_lengths (fine, shorter, Evoral::MusicalTime(), start, end); return true; @@ -780,14 +789,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,7 +911,7 @@ MidiRegionView::show_list_editor () * \param snap_t true to snap t to the grid, otherwise false. */ void -MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t) +MidiRegionView::create_note_at (framepos_t t, double y, Evoral::MusicalTime length, bool snap_t) { if (length < 2 * DBL_EPSILON) { return; @@ -1118,6 +1129,8 @@ MidiRegionView::redisplay_model() MidiModel::Notes& notes (_model->notes()); _optimization_iterator = _events.begin(); + bool empty_when_starting = _events.empty(); + for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { boost::shared_ptr note (*n); @@ -1125,8 +1138,8 @@ MidiRegionView::redisplay_model() bool visible; if (note_in_region_range (note, visible)) { - - if ((cne = find_canvas_note (note)) != 0) { + + if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { cne->validate (); @@ -1151,8 +1164,8 @@ MidiRegionView::redisplay_model() } } else { - - if ((cne = find_canvas_note (note)) != 0) { + + if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { cne->validate (); cne->hide (); } @@ -1162,21 +1175,23 @@ MidiRegionView::redisplay_model() /* remove note items that are no longer valid */ - 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); + if (!empty_when_starting) { + 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; } - - delete *i; - i = _events.erase (i); - - } else { - ++i; } } @@ -1290,7 +1305,7 @@ MidiRegionView::display_sysexes() } string text = str.str(); - const double x = trackview.editor().frame_to_pixel(source_beats_to_region_frames(time)); + const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time)); double height = midi_stream_view()->contents_height(); @@ -1344,11 +1359,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 @@ -1360,16 +1381,13 @@ MidiRegionView::reset_width_dependent_items (double pixel_width) redisplay_model(); } -// CAIROCANVAS -#if 0 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { - if ((*x)->width() >= _pixel_width) { + if ((*x)->canvas_item()->width() >= _pixel_width) { (*x)->hide(); } else { (*x)->show(); } } -#endif move_step_edit_cursor (_step_edit_cursor_position); set_step_edit_cursor_width (_step_edit_cursor_width); @@ -1378,17 +1396,15 @@ 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_pixbuf) { - name_pixbuf->raise_to_top(); + if (name_text) { + name_text->raise_to_top(); } for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { @@ -1431,17 +1447,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); } } } @@ -1451,7 +1464,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv) { Note* note; - double unit_position = _region->position () / frames_per_pixel; + double unit_position = _region->position () / samples_per_pixel; MidiTimeAxisView* mtv = dynamic_cast(&tv); MidiGhostRegion* ghost; @@ -1471,7 +1484,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv) } ghost->set_height (); - ghost->set_duration (_region->length() / frames_per_pixel); + ghost->set_duration (_region->length() / samples_per_pixel); ghosts.push_back (ghost); GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context()); @@ -1510,7 +1523,7 @@ 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::MusicalTime end_time) { if (midi_view()->note_mode() != Sustained) { return; @@ -1524,9 +1537,10 @@ MidiRegionView::resolve_note(uint8_t note, double end_time) */ const framepos_t end_time_frames = region_beats_to_region_frames(end_time); - _active_notes[note]->set_x1 (trackview.editor().frame_to_pixel(end_time_frames)); - _active_notes[note]->set_outline_what (0xf); + _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames)); + _active_notes[note]->set_outline_all (); _active_notes[note] = 0; + } } @@ -1542,7 +1556,7 @@ MidiRegionView::extend_active_notes() for (unsigned i=0; i < 128; ++i) { if (_active_notes[i]) { - _active_notes[i]->set_x1 (trackview.editor().frame_to_pixel(_region->length())); + _active_notes[i]->set_x1 (trackview.editor().sample_to_pixel(_region->length())); } } } @@ -1631,8 +1645,8 @@ void MidiRegionView::update_note (Note* ev, bool update_ghost_regions) { boost::shared_ptr note = ev->note(); - const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time())); - const double y0 = midi_stream_view()->note_to_y(note->note()); + const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time())); + const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note())); ev->set_x0 (x); ev->set_y0 (y0); @@ -1641,14 +1655,14 @@ MidiRegionView::update_note (Note* ev, bool update_ghost_regions) if (note->length() > 0) { const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length()); - ev->set_x1 (trackview.editor().frame_to_pixel (note_end_frames)); + ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames)); } else { - ev->set_x1 (trackview.editor().frame_to_pixel (_region->length())); + 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 @@ -1656,15 +1670,18 @@ MidiRegionView::update_note (Note* ev, bool update_ghost_regions) Note* const old_rect = _active_notes[note->note()]; boost::shared_ptr old_note = old_rect->note(); 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 (); } if (update_ghost_regions) { @@ -1677,19 +1694,18 @@ MidiRegionView::update_note (Note* ev, bool update_ghost_regions) } } -double +void MidiRegionView::update_hit (Hit* ev) { boost::shared_ptr note = ev->note(); 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); + const double x = trackview.editor().sample_to_pixel(note_start_frames); + const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.); + const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5; ev->set_position (ArdourCanvas::Duple (x, y)); - - return diamond_size; + ev->set_height (diamond_size); } /** Add a MIDI note to the view (with length). @@ -1703,8 +1719,6 @@ 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); @@ -1723,7 +1737,7 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) } 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); @@ -1805,7 +1819,7 @@ void MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/) { framecnt_t region_frames = source_beats_to_region_frames (patch->time()); - const double x = trackview.editor().frame_to_pixel (region_frames); + const double x = trackview.editor().sample_to_pixel (region_frames); double const height = midi_stream_view()->contents_height(); @@ -1844,13 +1858,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::MusicalTime 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::MusicalTime 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); @@ -2071,6 +2085,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. */ @@ -2083,6 +2103,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 @@ -2093,6 +2115,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; + } } } @@ -2134,32 +2161,39 @@ MidiRegionView::invert_selection () void MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend) { + bool have_selection = !_selection.empty(); uint8_t low_note = 127; uint8_t high_note = 0; MidiModel::Notes& notes (_model->notes()); _optimization_iterator = _events.begin(); + + if (extend && !have_selection) { + extend = false; + } + /* scan existing selection to get note range */ + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + if ((*i)->note()->note() < low_note) { + low_note = (*i)->note()->note(); + } + if ((*i)->note()->note() > high_note) { + high_note = (*i)->note()->note(); + } + } + if (!add) { clear_selection (); - } - if (extend && _selection.empty()) { - extend = false; + if (!extend && (low_note == high_note) && (high_note == notenum)) { + /* only note previously selected is the one we are + * reselecting. treat this as cancelling the selection. + */ + return; + } } if (extend) { - - /* scan existing selection to get note range */ - - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - if ((*i)->note()->note() < low_note) { - low_note = (*i)->note()->note(); - } - if ((*i)->note()->note() > high_note) { - high_note = (*i)->note()->note(); - } - } - low_note = min (low_note, notenum); high_note = max (high_note, notenum); } @@ -2241,7 +2275,7 @@ MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend) /* 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::MusicalTime latest = Evoral::MusicalTime(); for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { if ((*i)->note()->end_time() > latest) { @@ -2280,8 +2314,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. @@ -2297,6 +2341,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 @@ -2329,6 +2391,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); @@ -2343,18 +2410,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); } @@ -2374,7 +2442,7 @@ 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); @@ -2471,10 +2539,10 @@ MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) * @return Snapped frame relative to the region position. */ framepos_t -MidiRegionView::snap_pixel_to_frame(double x) +MidiRegionView::snap_pixel_to_sample(double x) { PublicEditor& editor (trackview.editor()); - return snap_frame_to_frame (editor.pixel_to_frame (x)); + return snap_frame_to_frame (editor.pixel_to_sample (x)); } /** @param x Pixel relative to the region position. @@ -2483,25 +2551,25 @@ MidiRegionView::snap_pixel_to_frame(double x) double MidiRegionView::snap_to_pixel(double x) { - return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x)); + return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x)); } double MidiRegionView::get_position_pixels() { framepos_t region_frame = get_position(); - return trackview.editor().frame_to_pixel(region_frame); + return trackview.editor().sample_to_pixel(region_frame); } double MidiRegionView::get_end_position_pixels() { framepos_t frame = get_position() + get_duration (); - return trackview.editor().frame_to_pixel(frame); + return trackview.editor().sample_to_pixel(frame); } framepos_t -MidiRegionView::source_beats_to_absolute_frames(double beats) const +MidiRegionView::source_beats_to_absolute_frames(Evoral::MusicalTime beats) const { /* the time converter will return the frame corresponding to `beats' relative to the start of the source. The start of the source @@ -2511,7 +2579,7 @@ MidiRegionView::source_beats_to_absolute_frames(double beats) const return source_start + _source_relative_time_converter.to (beats); } -double +Evoral::MusicalTime MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const { /* the `frames' argument needs to be converted into a frame count @@ -2523,12 +2591,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::MusicalTime beats) const { return _region_relative_time_converter.to(beats); } -double +Evoral::MusicalTime MidiRegionView::region_frames_to_region_beats(framepos_t frames) const { return _region_relative_time_converter.from(frames); @@ -2553,7 +2621,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()->canvasvar_MidiNoteSelected.get(), + ARDOUR_UI::config()->color ("midi note selected"), 128); // make the resize preview notes more transparent and bright @@ -2566,7 +2634,7 @@ MidiRegionView::begin_resizing (bool /*at_front*/) 0.85)); resize_rect->set_outline_color (NoteBase::calculate_outline ( - ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get())); + ARDOUR_UI::config()->color ("midi note selected"))); resize_data->resize_rect = resize_rect; _resize_data.push_back(resize_data); @@ -2607,6 +2675,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()); @@ -2616,30 +2693,23 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ } if (!cursor_set) { - double beats; - - beats = snap_pixel_to_frame (current_x); - beats = region_frames_to_region_beats (beats); - - double len; + const double snapped_x = snap_pixel_to_sample (current_x); + Evoral::MusicalTime beats = region_frames_to_region_beats (snapped_x); + Evoral::MusicalTime len = Evoral::MusicalTime(); 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; } } 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; @@ -2681,28 +2751,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_frame (current_x) + _region->start (); + 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::MusicalTime 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::MusicalTime 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(); + const Evoral::MusicalTime len = x_beats - canvas_note->note()->time(); - if (len > 0) { + if (!!len) { /* XXX convert to beats */ note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); } @@ -2764,8 +2840,8 @@ MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evo { bool change_start = false; bool change_length = false; - Evoral::MusicalTime new_start = 0; - Evoral::MusicalTime new_length = 0; + Evoral::MusicalTime new_start; + Evoral::MusicalTime new_length; /* NOTE: the semantics of the two delta arguments are slightly subtle: @@ -2776,11 +2852,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::MusicalTime(); } else { new_start = event->note()->time() + front_delta; // moves earlier } @@ -2808,7 +2884,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) { @@ -2861,7 +2937,7 @@ MidiRegionView::change_note_time (NoteBase* event, Evoral::MusicalTime delta, bo if (relative) { if (delta < 0.0) { if (event->note()->time() < -delta) { - new_time = 0; + new_time = Evoral::MusicalTime(); } else { new_time = event->note()->time() + delta; } @@ -2990,18 +3066,12 @@ 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) { - if (delta == 0.0) { + if (!delta) { if (fine) { - delta = 1.0/128.0; + delta = Evoral::MusicalTime(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()); } } @@ -3017,7 +3087,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::MusicalTime()), + (end ? delta : Evoral::MusicalTime())); i = next; } @@ -3026,7 +3098,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; @@ -3037,15 +3109,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::MusicalTime delta; + + if (!fine) { - if (trackview.editor().snap_mode() == Editing::SnapOff) { + /* non-fine, move by 1 bar regardless of snap */ + delta = Evoral::MusicalTime(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 { @@ -3064,16 +3142,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 (distance)); - if (!forward) { delta = -delta; } @@ -3107,7 +3184,7 @@ MidiRegionView::note_entered(NoteBase* ev) { Editor* editor = dynamic_cast(&trackview.editor()); - pre_enter_cursor = editor->get_canvas_cursor (); + pre_note_enter_cursor = editor->get_canvas_cursor (); if (_mouse_state == SelectTouchDragging) { note_selected (ev, true); @@ -3127,9 +3204,9 @@ MidiRegionView::note_left (NoteBase*) editor->verbose_cursor()->hide (); - if (pre_enter_cursor) { - editor->set_canvas_cursor (pre_enter_cursor); - pre_enter_cursor = 0; + if (pre_note_enter_cursor) { + editor->set_canvas_cursor (pre_note_enter_cursor); + pre_note_enter_cursor = 0; } } @@ -3181,13 +3258,13 @@ MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, boo 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); + if (can_set_cursor) { + 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_note_enter_cursor) { + editor->set_canvas_cursor (pre_note_enter_cursor); } } } @@ -3204,15 +3281,15 @@ MidiRegionView::set_frame_color() } if (_selected) { - f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get(); + f = ARDOUR_UI::config()->color ("selected region base"); } else if (high_enough_for_name) { - f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get(); + f= ARDOUR_UI::config()->color ("midi frame base"); } else { f = fill_color; } if (!rect_visible) { - f = UINT_RGBA_CHANGE_A (f, 0); + f = UINT_RGBA_CHANGE_A (f, 80); } frame->set_fill_color (f); @@ -3290,7 +3367,7 @@ MidiRegionView::selection_as_cut_buffer () const { Notes notes; - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) { NoteType* n = (*i)->note().get(); notes.insert (boost::shared_ptr (new NoteType (*n))); } @@ -3302,34 +3379,54 @@ MidiRegionView::selection_as_cut_buffer () const } /** This method handles undo */ -void -MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) +bool +MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx) { - if (mcb.empty()) { - return; + trackview.session()->begin_reversible_command (Operations::paste); + + // 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); } - DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times)); + // 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); + } - trackview.session()->begin_reversible_command (_("paste")); + trackview.session()->commit_reversible_command (); - start_note_diff_command (_("paste")); + return true; +} - Evoral::MusicalTime beat_delta; - Evoral::MusicalTime paste_pos_beats; - Evoral::MusicalTime duration; - Evoral::MusicalTime end_point = 0; +/** This method handles undo */ +void +MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb) +{ + if (mcb.empty()) { + return; + } - 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; + start_note_diff_command (_("paste")); - 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::MusicalTime snap_beats = get_grid_beats(pos); + const Evoral::MusicalTime first_time = (*mcb.notes().begin())->time(); + const Evoral::MusicalTime last_time = (*mcb.notes().rbegin())->end_time(); + const Evoral::MusicalTime duration = last_time - first_time; + const Evoral::MusicalTime snap_duration = duration.snap_to(snap_beats); + const Evoral::MusicalTime paste_offset = snap_duration * paste_count; + const Evoral::MusicalTime pos_beats = absolute_frames_to_source_beats(pos) + paste_offset; + Evoral::MusicalTime end_point = Evoral::MusicalTime(); + + 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 (); @@ -3338,15 +3435,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 */ @@ -3364,8 +3459,6 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) } apply_diff (true); - - trackview.session()->commit_reversible_command (); } struct EventNoteTimeEarlyFirstComparator { @@ -3494,24 +3587,21 @@ MidiRegionView::update_ghost_note (double x, double y) PublicEditor& editor = trackview.editor (); - framepos_t const unsnapped_frame = editor.pixel_to_frame (x); + framepos_t const unsnapped_frame = editor.pixel_to_sample (x); framecnt_t grid_frames; framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames); /* 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); - - if (!success) { - length = 1; - } + const Evoral::MusicalTime length = get_grid_beats(unsnapped_frame); /* 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 ( + std::max(Evoral::MusicalTime(), + 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 ()); @@ -3531,6 +3621,7 @@ MidiRegionView::create_ghost_note (double x, double y) _ghost_note = new Note (*this, _note_group, g); _ghost_note->set_ignore_events (true); _ghost_note->set_outline_color (0x000000aa); + if (x < 0) { x = 0; } update_ghost_note (x, y); _ghost_note->show (); @@ -3540,6 +3631,13 @@ MidiRegionView::create_ghost_note (double x, double y) show_verbose_cursor (_ghost_note->note ()); } +void +MidiRegionView::remove_ghost_note () +{ + delete _ghost_note; + _ghost_note = 0; +} + void MidiRegionView::snap_changed () { @@ -3559,6 +3657,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); @@ -3616,7 +3716,7 @@ void MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime 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); @@ -3635,7 +3735,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 (region_beats_to_region_frames (pos)); + double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos)); _step_edit_cursor->set_x0 (pixel); set_step_edit_cursor_width (_step_edit_cursor_width); } @@ -3655,7 +3755,7 @@ MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats) _step_edit_cursor_width = beats; if (_step_edit_cursor) { - _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats))); + _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats))); } } @@ -3704,7 +3804,7 @@ MidiRegionView::data_recorded (boost::weak_ptr w) 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::MusicalTime(), ev.note(), ev.velocity())); add_note (note, true); @@ -3731,7 +3831,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); } @@ -3754,8 +3854,6 @@ MidiRegionView::edit_patch_change (PatchChange* pc) { PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true); - d.set_position (Gtk::WIN_POS_MOUSE); - int response = d.run(); switch (response) { @@ -3818,28 +3916,9 @@ 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; - - /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */ - - boost::optional bbo = trackview.editor().verbose_cursor()->item().bounding_box(); - - assert (bbo); - - ArdourCanvas::Rect bb = bbo.get(); - - if ((wy + bb.y1 - bb.y0) > trackview.editor().visible_canvas_height()) { - wy -= (bb.y1 - bb.y0) + 2 * yoffset; - } - - trackview.editor().verbose_cursor()->set_position (wx, wy); + trackview.editor().verbose_cursor()->set (text); trackview.editor().verbose_cursor()->show (); + trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset)); } /** @param p A session framepos. @@ -3851,13 +3930,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::MusicalTime 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 @@ -3905,3 +3979,15 @@ MidiRegionView::get_selected_channels () const return rtav->midi_track()->get_playback_channel_mask(); } + +Evoral::MusicalTime +MidiRegionView::get_grid_beats(framepos_t pos) const +{ + PublicEditor& editor = trackview.editor(); + bool success = false; + Evoral::MusicalTime beats = editor.get_grid_type_as_beats(success, pos); + if (!success) { + beats = Evoral::MusicalTime(1); + } + return beats; +}