X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=19725b375fb0a68128e03bc7c058fafa3190aa52;hb=b426873f6fd05ad546e49317cc6dc7ee59df60fa;hp=273ba0dbd74d7871a3871b12c5cabe39e1b02705;hpb=279aefc60e232475c771932744afc1304f0b92d5;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 273ba0dbd7..19725b375f 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -35,23 +35,26 @@ #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" @@ -68,7 +71,6 @@ #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 +84,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) @@ -112,6 +120,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _last_event_y (0) , 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())); @@ -124,14 +133,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) @@ -149,6 +163,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _last_event_y (0) , 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())); @@ -164,7 +179,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(); } @@ -176,8 +191,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) @@ -195,23 +212,20 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _last_event_y (0) , 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) @@ -229,19 +243,14 @@ 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()); @@ -322,6 +329,10 @@ MidiRegionView::connect_to_diskstream () bool MidiRegionView::canvas_group_event(GdkEvent* ev) { + if (in_destructor) { + return false; + } + bool r; switch (ev->type) { @@ -348,15 +359,17 @@ MidiRegionView::canvas_group_event(GdkEvent* ev) } if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) || - (trackview.editor().current_mouse_mode() == MouseTimeFX) || - (trackview.editor().current_mouse_mode() == MouseZoom)) { + (trackview.editor().current_mouse_mode() == MouseTimeFX)) { // handle non-internal-edit/non-draw modes elsewhere 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); @@ -374,9 +387,13 @@ MidiRegionView::canvas_group_event(GdkEvent* ev) return r; case GDK_ENTER_NOTIFY: + // set entered_regionview (among other things) + trackview.editor().canvas_region_view_event (ev, group, this); return enter_notify (&ev->crossing); case GDK_LEAVE_NOTIFY: + // reset entered_regionview (among other things) + trackview.editor().canvas_region_view_event (ev, group, this); return leave_notify (&ev->crossing); case GDK_MOTION_NOTIFY: @@ -425,9 +442,14 @@ MidiRegionView::leave_notify (GdkEventCrossing*) trackview.editor().verbose_cursor()->hide (); remove_ghost_note (); + if (trackview.editor().internal_editing()) { + Keyboard::magic_widget_drop_focus(); + } + if (pre_enter_cursor) { Editor* editor = dynamic_cast (&trackview.editor()); editor->set_canvas_cursor(pre_enter_cursor); + pre_enter_cursor = 0; } return false; @@ -464,7 +486,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) { @@ -523,17 +545,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::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_sample (event_x), event_y, beats, true); } @@ -542,17 +559,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::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_sample (event_x), event_y, beats, true); @@ -716,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; @@ -897,7 +909,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; @@ -1345,11 +1357,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 @@ -1427,8 +1445,8 @@ 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); @@ -1503,7 +1521,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; @@ -1626,7 +1644,7 @@ MidiRegionView::update_note (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); @@ -1640,9 +1658,9 @@ 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 @@ -1681,8 +1699,8 @@ MidiRegionView::update_hit (Hit* ev) 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(); - const double y = midi_stream_view()->note_to_y(note->note()) + (diamond_size/2.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); @@ -1717,7 +1735,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); @@ -1838,13 +1856,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); @@ -2242,7 +2260,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) { @@ -2281,8 +2299,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. @@ -2298,6 +2326,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 @@ -2375,7 +2421,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); @@ -2502,7 +2548,7 @@ MidiRegionView::get_end_position_pixels() } 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 @@ -2512,7 +2558,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 @@ -2524,12 +2570,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); @@ -2554,7 +2600,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()->get_MidiNoteSelected(), 128); // make the resize preview notes more transparent and bright @@ -2567,7 +2613,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()->get_MidiNoteSelected())); resize_data->resize_rect = resize_rect; _resize_data.push_back(resize_data); @@ -2608,6 +2654,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()); @@ -2617,30 +2672,23 @@ 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::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; @@ -2682,28 +2730,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::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); } @@ -2765,8 +2819,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: @@ -2777,11 +2831,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 } @@ -2809,7 +2863,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,7 +2916,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; } @@ -2991,18 +3045,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()); } } @@ -3018,7 +3066,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; } @@ -3065,7 +3115,7 @@ MidiRegionView::nudge_notes (bool forward) next_pos -= 1; } - trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false); + trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false); distance = ref_point - next_pos; } @@ -3108,7 +3158,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); @@ -3128,9 +3178,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; } } @@ -3182,13 +3232,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); } } } @@ -3205,15 +3255,15 @@ MidiRegionView::set_frame_color() } if (_selected) { - f = ARDOUR_UI::config()->get_canvasvar_SelectedFrameBase(); + f = ARDOUR_UI::config()->get_SelectedFrameBase(); } else if (high_enough_for_name) { - f= ARDOUR_UI::config()->get_canvasvar_MidiFrameBase(); + f= ARDOUR_UI::config()->get_MidiFrameBase(); } 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); @@ -3303,34 +3353,57 @@ 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, unsigned paste_count, float times, const ::Selection& selection, ItemCounts& counts) { - if (mcb.empty()) { - return; + // Get our set of notes from the selection + MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(counts.n_notes()); + if (m == selection.midi_notes.end()) { + return false; } + counts.increase_n_notes(); - DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times)); + trackview.session()->begin_reversible_command (Operations::paste); - trackview.session()->begin_reversible_command (_("paste")); + // Paste notes + paste_internal(pos, paste_count, times, **m); - start_note_diff_command (_("paste")); + // Paste control points to automation children + 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, paste_count, times, selection, counts); + } + + trackview.session()->commit_reversible_command (); - Evoral::MusicalTime beat_delta; - Evoral::MusicalTime paste_pos_beats; - Evoral::MusicalTime duration; - Evoral::MusicalTime end_point = 0; + return true; +} - 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; +/** This method handles undo */ +void +MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb) +{ + if (mcb.empty()) { + return; + } - 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(), + start_note_diff_command (_("paste")); + + 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 (); @@ -3339,15 +3412,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 */ @@ -3365,8 +3436,6 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) } apply_diff (true); - - trackview.session()->commit_reversible_command (); } struct EventNoteTimeEarlyFirstComparator { @@ -3502,17 +3571,14 @@ MidiRegionView::update_ghost_note (double x, double y) /* 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 ()); @@ -3532,6 +3598,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 (); @@ -3624,7 +3691,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); @@ -3712,7 +3779,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); @@ -3739,7 +3806,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); } @@ -3824,28 +3891,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. @@ -3857,13 +3905,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 @@ -3911,3 +3954,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; +}