X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=79933b94d6cdb58afb0877c796720801733885c3;hb=cf52d6e4b40111eb04b244ec054055a4ec15dbe0;hp=75b7d4693aa788bede8b84403539f928c46b0da1;hpb=4973ddc4783756123766b51731107b74bd163477;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 75b7d4693a..79933b94d6 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -33,6 +33,7 @@ #include "pbd/stateful_diff_command.h" #include "ardour/midi_model.h" +#include "ardour/midi_playlist.h" #include "ardour/midi_region.h" #include "ardour/midi_source.h" #include "ardour/midi_track.h" @@ -75,13 +76,13 @@ #include "streamview.h" #include "patch_change_dialog.h" #include "verbose_cursor.h" -#include "ardour_ui.h" #include "note.h" #include "hit.h" #include "patch_change.h" #include "sys_ex.h" +#include "ui_config.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace ARDOUR; using namespace PBD; @@ -89,8 +90,6 @@ 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::Container* parent, @@ -103,6 +102,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _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()) + , _region_relative_time_converter_double(r->session().tempo_map(), r->position()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) @@ -118,10 +118,13 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) + , _last_display_zoom (0) , _last_event_x (0) , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _note_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(); @@ -129,11 +132,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, 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 ()); } MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, @@ -148,8 +146,9 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _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()) + , _region_relative_time_converter_double(r->session().tempo_map(), r->position()) , _active_notes(0) - , _note_group (new ArdourCanvas::Container (parent)) + , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) , _ghost_note(0) , _step_edit_cursor (0) @@ -163,10 +162,13 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) + , _last_display_zoom (0) , _last_event_x (0) , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) + , _note_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(); @@ -174,11 +176,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); connect_to_diskstream (); - - SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); - - 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 @@ -198,6 +195,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _current_range_max(0) , _region_relative_time_converter(other.region_relative_time_converter()) , _source_relative_time_converter(other.source_relative_time_converter()) + , _region_relative_time_converter_double(other.region_relative_time_converter_double()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (get_canvas_group())) , _note_diff_command (0) @@ -213,10 +211,13 @@ 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) , _grabbed_keyboard (false) , _entered (false) + , _note_entered (false) + , _mouse_changed_selection (false) { init (false); } @@ -227,6 +228,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrmidi_source(0)->mutex()); midi_region()->midi_source(0)->load_model(lm); @@ -270,12 +271,12 @@ MidiRegionView::init (bool wfd) RegionView::init (false); - set_height (trackview.current_height()); + //set_height (trackview.current_height()); region_muted (); region_sync_changed (); region_resized (ARDOUR::bounds_change); - region_locked (); + //region_locked (); set_colors (); @@ -290,7 +291,7 @@ MidiRegionView::init (bool wfd) group->raise_to_top(); - midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this), + midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this), boost::bind (&MidiRegionView::midi_channel_mode_changed, this), gui_context ()); @@ -307,11 +308,6 @@ MidiRegionView::init (bool wfd) 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& @@ -382,7 +378,6 @@ MidiRegionView::canvas_group_event(GdkEvent* ev) case GDK_BUTTON_RELEASE: r = button_release (&ev->button); - _note_player.reset(); return r; case GDK_MOTION_NOTIFY: @@ -422,13 +417,26 @@ MidiRegionView::mouse_mode_changed () 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 + if (!trackview.editor().internal_editing()) { + /* Switched out of internal editing mode while entered. + Only necessary for leave as a mouse_mode_change over a region + automatically triggers an enter event. */ leave_internal(); } + else if (trackview.editor().current_mouse_mode() == MouseContent) { + // hide cursor and ghost note after changing to internal edit mode + remove_ghost_note (); + + /* XXX This is problematic as the function is executed for every region + and only for one region _note_entered can be true. Still it's + necessary as to hide the verbose cursor when we're changing from + draw mode to internal edit mode. These lines are the reason why + in some situations no verbose cursor is shown when we enter internal + edit mode over a note. */ + if (!_note_entered) { + hide_verbose_cursor (); + } + } } } @@ -458,8 +466,9 @@ MidiRegionView::enter_internal() void MidiRegionView::leave_internal() { - trackview.editor().verbose_cursor()->hide (); + hide_verbose_cursor (); remove_ghost_note (); + _note_entered = false; if (_grabbed_keyboard) { Keyboard::magic_widget_drop_focus(); @@ -490,14 +499,15 @@ MidiRegionView::button_press (GdkEventButton* ev) } if (_mouse_state != SelectTouchDragging) { - + _pressed_button = ev->button; _mouse_state = Pressed; - + return true; } _pressed_button = ev->button; + _mouse_changed_selection = false; return true; } @@ -526,14 +536,15 @@ MidiRegionView::button_release (GdkEventButton* ev) switch (editor.current_mouse_mode()) { case MouseRange: - /* no motion occured - simple click */ - clear_selection (); + /* no motion occurred - simple click */ + clear_editor_note_selection (); + _mouse_changed_selection = true; break; case MouseContent: case MouseTimeFX: { - clear_selection(); + _mouse_changed_selection = true; if (Keyboard::is_insert_note_event(ev)) { @@ -543,29 +554,18 @@ MidiRegionView::button_release (GdkEventButton* ev) event_y = ev->y; group->canvas_to_item (event_x, event_y); - 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 -= Evoral::MusicalTime::tick(); - + Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x)); create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); + } else { + clear_editor_note_selection (); } break; } case MouseDraw: { - 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 -= Evoral::MusicalTime::tick(); - + Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x)); create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true); - break; } default: @@ -575,11 +575,12 @@ MidiRegionView::button_release (GdkEventButton* ev) _mouse_state = None; break; - case SelectRectDragging: case AddDragging: + /* Only create a ghost note when we added a note, not when we were drag-selecting. */ + create_ghost_note (ev->x, ev->y); + case SelectRectDragging: editor.drags()->end_grab ((GdkEvent *) ev); _mouse_state = None; - create_ghost_note (ev->x, ev->y); break; @@ -587,6 +588,11 @@ MidiRegionView::button_release (GdkEventButton* ev) break; } + if (_mouse_changed_selection) { + trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change")); + trackview.editor().commit_reversible_selection_op (); + } + return false; } @@ -595,25 +601,33 @@ MidiRegionView::motion (GdkEventMotion* ev) { PublicEditor& editor = trackview.editor (); - if (!_ghost_note && editor.current_mouse_mode() == MouseContent && - Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) && - _mouse_state != AddDragging) { + if (!_note_entered) { - create_ghost_note (ev->x, ev->y); + if (!_ghost_note && editor.current_mouse_mode() == MouseContent && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) && + _mouse_state != AddDragging) { - } else if (_ghost_note && editor.current_mouse_mode() == MouseContent && - Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { + create_ghost_note (ev->x, ev->y); - update_ghost_note (ev->x, ev->y); + } else if (_ghost_note && editor.current_mouse_mode() == MouseContent && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { - } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) { + update_ghost_note (ev->x, ev->y); - remove_ghost_note (); - editor.verbose_cursor()->hide (); + } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) { + + remove_ghost_note (); + hide_verbose_cursor (); - } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) { + } else if (editor.current_mouse_mode() == MouseDraw) { - update_ghost_note (ev->x, ev->y); + if (_ghost_note) { + update_ghost_note (ev->x, ev->y); + } + else { + create_ghost_note (ev->x, ev->y); + } + } } /* any motion immediately hides velocity text that may have been visible */ @@ -626,19 +640,20 @@ MidiRegionView::motion (GdkEventMotion* ev) case Pressed: if (_pressed_button == 1) { - + MouseMode m = editor.current_mouse_mode(); - + 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 (); + hide_verbose_cursor (); return true; } else if (m == MouseContent) { editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { - clear_selection (); + clear_editor_note_selection (); + _mouse_changed_selection = true; } _mouse_state = SelectRectDragging; return true; @@ -656,7 +671,7 @@ MidiRegionView::motion (GdkEventMotion* ev) case AddDragging: editor.drags()->motion_handler ((GdkEvent *) ev, false); break; - + case SelectTouchDragging: return false; @@ -665,7 +680,7 @@ MidiRegionView::motion (GdkEventMotion* ev) } - /* we may be dragging some non-note object (eg. patch-change, sysex) + /* we may be dragging some non-note object (eg. patch-change, sysex) */ return editor.drags()->motion_handler ((GdkEvent *) ev, false); @@ -679,17 +694,19 @@ MidiRegionView::scroll (GdkEventScroll* ev) return false; } - if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { - /* XXX: bit of a hack; allow PrimaryModifier scroll through so that - it still works for zoom. + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) || + Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll + * through so that it still works for navigation. */ return false; } - trackview.editor().verbose_cursor()->hide (); + hide_verbose_cursor (); bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); - bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); + Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier); + bool together = Keyboard::modifier_state_contains (ev->state, mask_together); if (ev->direction == GDK_SCROLL_UP) { change_velocities (true, fine, false, together); @@ -712,23 +729,23 @@ MidiRegionView::key_press (GdkEventKey* ev) */ bool unmodified = Keyboard::no_modifier_keys_pressed (ev); - + if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) { _mouse_state = SelectTouchDragging; return true; } else if (ev->keyval == GDK_Escape && unmodified) { - clear_selection(); + clear_editor_note_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, Evoral::MusicalTime(), start, end); + change_note_lengths (fine, shorter, Evoral::Beats(), start, end); return true; @@ -741,27 +758,19 @@ MidiRegionView::key_press (GdkEventKey* ev) delete_selection(); return true; - } else if (ev->keyval == GDK_Tab) { - - 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)); - } - return true; - - } else if (ev->keyval == GDK_ISO_Left_Tab) { + } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) { - /* Shift-TAB generates ISO Left Tab, for some reason */ + trackview.editor().begin_reversible_selection_op (X_("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)); } - return true; + trackview.editor().commit_reversible_selection_op(); + return true; } else if (ev->keyval == GDK_Up) { @@ -865,7 +874,7 @@ MidiRegionView::velocity_edit () if (_selection.empty()) { return; } - + /* pick a note somewhat at random (since Selection is a set<>) to * provide the "current" velocity for the dialog. */ @@ -911,28 +920,38 @@ 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, Evoral::MusicalTime 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(); + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + MidiStreamView* const view = mtv->midi_view(); + boost::shared_ptr mr = boost::dynamic_pointer_cast (_region); - const double note = view->y_to_note(y); + if (!mr) { + return; + } // Start of note in frames relative to region start + uint32_t divisions = 0; + if (snap_t) { framecnt_t grid_frames; t = snap_frame_to_grid_underneath (t, grid_frames); + divisions = trackview.editor().get_grid_music_divisions (0); } - 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 = Evoral::Beats (trackview.session()->tempo_map().exact_beat_at_frame (_region->position() + t, divisions) + - (mr->beat() - mr->start_beats().to_double())); + 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; @@ -940,17 +959,21 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::MusicalTime leng view->update_note_range(new_note->note()); - MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note")); - cmd->add (new_note); - _model->apply_command(*trackview.session(), cmd); + start_note_diff_command(_("add note")); + + clear_editor_note_selection (); + note_diff_add_note (new_note, true, false); + + apply_diff(); play_midi_note (new_note); } void -MidiRegionView::clear_events (bool with_selection_signal) +MidiRegionView::clear_events () { - clear_selection (with_selection_signal); + // clear selection without signaling + clear_selection_internal (); MidiGhostRegion* gr; for (std::vector::iterator g = ghosts.begin(); g != ghosts.end(); ++g) { @@ -976,7 +999,7 @@ 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()); - + /* Don't signal as nobody else needs to know until selection has been altered. */ clear_events (); if (_enable_display) { @@ -988,6 +1011,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); } } @@ -1027,7 +1051,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); @@ -1038,6 +1062,7 @@ void MidiRegionView::apply_diff (bool as_subcommand) { bool add_or_remove; + bool commit = false; if (!_note_diff_command) { return; @@ -1050,20 +1075,26 @@ MidiRegionView::apply_diff (bool as_subcommand) } } + midi_view()->midi_track()->midi_playlist()->region_edited( + _region, _note_diff_command); + if (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; - midi_view()->midi_track()->playlist_modified(); if (add_or_remove) { _marked_for_selection.clear(); } _marked_for_velocity.clear(); + if (commit) { + trackview.editor().commit_reversible_command (); + } } void @@ -1071,7 +1102,7 @@ MidiRegionView::abort_command() { delete _note_diff_command; _note_diff_command = 0; - clear_selection(); + clear_editor_note_selection(); } NoteBase* @@ -1094,8 +1125,23 @@ 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) +{ + Events::iterator it; + + for (it = _events.begin(); it != _events.end(); ++it) { + if (*((*it)->note()) == note) { + return *it; + } + } + + 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); @@ -1111,8 +1157,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; } @@ -1130,29 +1188,20 @@ MidiRegionView::redisplay_model() _optimization_iterator = _events.begin(); bool empty_when_starting = _events.empty(); + NoteBase* cne; for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { boost::shared_ptr note (*n); - NoteBase* cne; bool visible; if (note_in_region_range (note, visible)) { - - 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); - } + if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { if (visible) { + cne->validate (); + update_note (cne); cne->show (); } else { cne->hide (); @@ -1160,11 +1209,18 @@ MidiRegionView::redisplay_model() } else { - add_note (note, visible); + cne = add_note (note, visible); + } + + set >::iterator it; + for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) { + if (*(*it) == *note) { + add_to_selection (cne); + } } } else { - + if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { cne->validate (); cne->hide (); @@ -1172,23 +1228,22 @@ MidiRegionView::redisplay_model() } } - /* remove note items that are no longer valid */ 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; } @@ -1203,6 +1258,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 @@ -1246,12 +1302,12 @@ MidiRegionView::display_sysexes() bool have_periodic_system_messages = false; bool display_periodic_messages = true; - if (!ARDOUR_UI::config()->get_never_display_periodic_midi()) { + if (!UIConfiguration::instance().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()) { have_periodic_system_messages = true; @@ -1259,22 +1315,22 @@ MidiRegionView::display_sysexes() } } } - + if (have_periodic_system_messages) { double zoom = trackview.editor().get_current_zoom (); // frames per pixel - + /* get an approximate value for the number of samples per video frame */ - + double video_frame = trackview.session()->frame_rate() * (1.0/30); - + /* if we are zoomed out beyond than the cutoff (i.e. more * frames per pixel than frames per 4 video frames), don't * show periodic sysex messages. */ - + if (zoom > (video_frame*4)) { display_periodic_messages = false; - } + } } } else { display_periodic_messages = false; @@ -1282,10 +1338,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()) { @@ -1330,9 +1386,7 @@ MidiRegionView::~MidiRegionView () { in_destructor = true; - trackview.editor().verbose_cursor()->hide (); - - note_delete_connection.disconnect (); + hide_verbose_cursor (); delete _list_editor; @@ -1342,10 +1396,8 @@ MidiRegionView::~MidiRegionView () end_write(); } - _selection_cleared_connection.disconnect (); - _selection.clear(); - clear_events (false); + clear_events (); delete _note_group; delete _note_diff_command; @@ -1356,20 +1408,24 @@ MidiRegionView::~MidiRegionView () void MidiRegionView::region_resized (const PropertyChange& what_changed) { - RegionView::region_resized(what_changed); + RegionView::region_resized(what_changed); // calls RegionView::set_duration() 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(); - } + _region_relative_time_converter_double.set_origin_b(_region->position()); + /* reset_width dependent_items() redisplays 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()); } + /* catch an end trim so we can live update */ + if (!what_changed.contains (ARDOUR::Properties::start) && + what_changed.contains (ARDOUR::Properties::length)) { + enable_display (true); + } } void @@ -1379,7 +1435,7 @@ MidiRegionView::reset_width_dependent_items (double pixel_width) if (_enable_display) { redisplay_model(); - } + } for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { if ((*x)->canvas_item()->width() >= _pixel_width) { @@ -1450,6 +1506,21 @@ MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force) 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.); + if (y0 < 0 || y1 >= _height) { + /* During DnD, the region uses the 'old/current' + * midi_stream_view()'s range and its position/height calculation. + * + * Ideally DnD would decouple the midi_stream_view() for the + * region(s) being dragged and set it to the target's range + * (or in case of the drop-zone, FullRange). + * but I don't see how this can be done without major rework. + * + * For now, just prevent visual bleeding of events in case + * the target-track is smaller. + */ + event->hide(); + continue; + } cnote->set_y0 (y0); cnote->set_y1 (y1); @@ -1462,8 +1533,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; @@ -1472,23 +1541,19 @@ MidiRegionView::add_ghost (TimeAxisView& tv) /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group to allow having midi notes on top of note lines and waveforms. */ - ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position); + ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position); } else { - ghost = new MidiGhostRegion (tv, trackview, unit_position); + ghost = new MidiGhostRegion (*this, tv, trackview, unit_position); } 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 (); 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()); - return ghost; } @@ -1523,24 +1588,24 @@ MidiRegionView::end_write() /** Resolve an active MIDI note (while recording). */ void -MidiRegionView::resolve_note(uint8_t note, Evoral::MusicalTime 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_all (); _active_notes[note] = 0; - } } @@ -1554,19 +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->position() + _region->length())); + trackview.editor().sample_to_pixel(_region->length())); } } } - void MidiRegionView::play_midi_note(boost::shared_ptr note) { - if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) { + if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) { return; } @@ -1593,7 +1657,7 @@ MidiRegionView::start_playing_midi_note(boost::shared_ptr note) void MidiRegionView::start_playing_midi_chord (vector > notes) { - if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) { + if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) { return; } @@ -1603,21 +1667,22 @@ MidiRegionView::start_playing_midi_chord (vector > n return; } - _note_player = boost::shared_ptr(new NotePlayer(route_ui->midi_track())); + NotePlayer* player = new NotePlayer (route_ui->midi_track()); for (vector >::iterator n = notes.begin(); n != notes.end(); ++n) { - _note_player->add (*n); + player->add (*n); } - _note_player->on (); + player->play (); } bool MidiRegionView::note_in_region_range (const boost::shared_ptr note, bool& visible) const { - const framepos_t note_start_frames = source_beats_to_region_frames (note->time()); - bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame()); + const boost::shared_ptr midi_reg = midi_region(); + const bool outside = (note->time() < midi_reg->start_beats() || + note->time() > midi_reg->start_beats() + midi_reg->length_beats()); visible = (note->note() >= midi_stream_view()->lowest_note()) && (note->note() <= midi_stream_view()->highest_note()); @@ -1625,39 +1690,63 @@ 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) { + TempoMap& map (trackview.session()->tempo_map()); + const boost::shared_ptr mr = midi_region(); boost::shared_ptr note = ev->note(); - const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time())); + const framepos_t note_start_frames = map.frame_at_beat (_region->beat() - mr->start_beats().to_double() + + note->time().to_double()) - _region->position(); + + const double x0 = trackview.editor().sample_to_pixel (note_start_frames); + double x1; const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note())); + double y1;/* trim note display to not overlap the end of its region */ - ev->set_x0 (x); - ev->set_y0 (y0); + if (note->length() > 0) { + Evoral::Beats note_end_time = note->end_time(); - /* trim note display to not overlap the end of its region */ + if (note->end_time() > mr->start_beats() + mr->length_beats()) { + note_end_time = mr->start_beats() + mr->length_beats(); + } - 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().sample_to_pixel (note_end_frames)); + const framepos_t note_end_frames = map.frame_at_beat (_region->beat() - mr->start_beats().to_double() + + note_end_time.to_double()) - _region->position(); + + x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1; } else { - ev->set_x1 (trackview.editor().sample_to_pixel (_region->length())); + x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1; } - ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1)); + y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1); + + ArdourCanvas::Rect rect (x0, y0, x1, y1); + ev->set (rect); 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(); - old_rect->set_x1 (x); + 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 (x1); old_rect->set_outline_all (); } _active_notes[note->note()] = ev; @@ -1673,8 +1762,9 @@ MidiRegionView::update_note (Note* ev, bool update_ghost_regions) } // 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())); + //const uint32_t base_col = ev->base_color(); + //ev->set_fill_color(base_col); + //ev->set_outline_color(ev->calculate_outline(base_col, ev->selected())); if (update_ghost_regions) { for (std::vector::iterator i = ghosts.begin(); i != ghosts.end(); ++i) { @@ -1687,17 +1777,38 @@ MidiRegionView::update_note (Note* ev, bool update_ghost_regions) } void -MidiRegionView::update_hit (Hit* ev) +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 framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_beat (_region->beat() - midi_region()->start_beats().to_double() + + note->time().to_double()) - _region->position(); 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; + // see DnD note in MidiRegionView::apply_note_range() above + if (y <= 0 || y >= _height) { + ev->hide(); + } else { + ev->show(); + } + 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())); + + 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). @@ -1706,7 +1817,7 @@ 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; @@ -1715,18 +1826,10 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) 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 = std::max(1., floor(midi_stream_view()->note_height()) - 2.); @@ -1742,6 +1845,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); } @@ -1764,11 +1875,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)); @@ -1778,7 +1890,8 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity framepos_t region_end = _region->last_frame(); if (end_frame > region_end) { - _region->set_length (end_frame - _region->position()); + /* XX sets length in beats from audio space. make musical */ + _region->set_length (end_frame - _region->position(), 0); } MidiTimeAxisView* const mtv = dynamic_cast(&trackview); @@ -1787,17 +1900,19 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity view->update_note_range(new_note->note()); _marked_for_selection.clear (); - clear_selection (); start_note_diff_command (_("step add")); + + clear_editor_note_selection (); note_diff_add_note (new_note, true, false); + apply_diff(); // last_step_edit_note = new_note; } void -MidiRegionView::step_sustain (Evoral::MusicalTime beats) +MidiRegionView::step_sustain (Evoral::Beats beats) { change_note_lengths (false, false, beats, false, true); } @@ -1850,13 +1965,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, Evoral::MusicalTime 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 (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const + +void +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); @@ -1880,7 +1995,9 @@ MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MID 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()) { c->change_program (pc.patch (), new_patch.program()); @@ -1892,15 +2009,18 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri } _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()); @@ -1919,6 +2039,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 (); @@ -1930,13 +2051,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() ) @@ -1944,17 +2067,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 (); @@ -1963,9 +2089,11 @@ 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 (); @@ -1984,7 +2112,7 @@ MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta) } void -MidiRegionView::maybe_remove_deleted_note_from_selection (NoteBase* cne) +MidiRegionView::note_deleted (NoteBase* cne) { if (_selection.empty()) { return; @@ -2011,6 +2139,7 @@ MidiRegionView::delete_selection() _selection.clear(); apply_diff (); + hide_verbose_cursor (); } void @@ -2020,69 +2149,54 @@ MidiRegionView::delete_note (boost::shared_ptr n) _note_diff_command->remove (n); apply_diff (); - trackview.editor().verbose_cursor()->hide (); + hide_verbose_cursor (); +} + +void +MidiRegionView::clear_editor_note_selection () +{ + DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n"); + PublicEditor& editor(trackview.editor()); + editor.get_selection().clear_midi_notes(); } void -MidiRegionView::clear_selection_except (NoteBase* ev, bool signal) +MidiRegionView::clear_selection () { - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { - if ((*i) != ev) { - Selection::iterator tmp = i; - ++tmp; + clear_selection_internal(); + PublicEditor& editor(trackview.editor()); + editor.get_selection().remove(this); +} - (*i)->set_selected (false); - (*i)->hide_velocity (); - _selection.erase (i); +void +MidiRegionView::clear_selection_internal () +{ + DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n"); - i = tmp; - } else { - ++i; - } + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + (*i)->set_selected(false); + (*i)->hide_velocity(); } + _selection.clear(); - if (!ev && _entered) { + if (_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. - */ - - if (signal) { - SelectionCleared (this); /* EMIT 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 - regionview from the editor selection, since we're about to add - another note, and thus put/keep this regionview in the editor - selection anyway. - */ - - if (!ev->selected()) { - 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; - } - } + clear_editor_note_selection(); + add_to_selection(ev); } void MidiRegionView::select_all_notes () { - clear_selection (); + clear_editor_note_selection (); for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { add_to_selection (*i); @@ -2092,7 +2206,7 @@ MidiRegionView::select_all_notes () void MidiRegionView::select_range (framepos_t start, framepos_t end) { - clear_selection (); + clear_editor_note_selection (); for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { framepos_t t = source_beats_to_absolute_frames((*i)->note()->time()); @@ -2114,6 +2228,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) { @@ -2122,13 +2254,13 @@ MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, b 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(); @@ -2137,9 +2269,9 @@ MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, b high_note = (*i)->note()->note(); } } - + if (!add) { - clear_selection (); + clear_editor_note_selection (); if (!extend && (low_note == high_note) && (high_note == notenum)) { /* only note previously selected is the one we are @@ -2214,11 +2346,8 @@ void MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend) { if (!add) { - clear_selection_except (ev); - if (!_selection.empty()) { - PublicEditor& editor (trackview.editor()); - editor.get_selection().add (this); - } + clear_editor_note_selection(); + add_to_selection (ev); } if (!extend) { @@ -2230,8 +2359,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 = Evoral::MusicalTime(); + 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) { @@ -2389,7 +2518,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) { @@ -2404,7 +2533,7 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy) (*i)->move_event(dx, dy); } - if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) { + if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) { if (to_play.size() > 1) { @@ -2460,9 +2589,9 @@ MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) start_note_diff_command (_("move notes")); 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; @@ -2492,22 +2621,25 @@ MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) } /** @param x Pixel relative to the region position. + * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap. + * Used for inverting the snap logic with key modifiers and snap delta calculation. * @return Snapped frame relative to the region position. */ framepos_t -MidiRegionView::snap_pixel_to_sample(double x) +MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap) { PublicEditor& editor (trackview.editor()); - return snap_frame_to_frame (editor.pixel_to_sample (x)); + return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap); } /** @param x Pixel relative to the region position. + * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation). * @return Snapped pixel relative to the region position. */ double -MidiRegionView::snap_to_pixel(double x) +MidiRegionView::snap_to_pixel(double x, bool ensure_snap) { - return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x)); + return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap)); } double @@ -2525,7 +2657,7 @@ MidiRegionView::get_end_position_pixels() } framepos_t -MidiRegionView::source_beats_to_absolute_frames(Evoral::MusicalTime 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 @@ -2535,7 +2667,7 @@ MidiRegionView::source_beats_to_absolute_frames(Evoral::MusicalTime beats) const return source_start + _source_relative_time_converter.to (beats); } -Evoral::MusicalTime +Evoral::Beats MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const { /* the `frames' argument needs to be converted into a frame count @@ -2547,17 +2679,23 @@ MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const } framepos_t -MidiRegionView::region_beats_to_region_frames(Evoral::MusicalTime beats) const +MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const { return _region_relative_time_converter.to(beats); } -Evoral::MusicalTime +Evoral::Beats MidiRegionView::region_frames_to_region_beats(framepos_t frames) const { return _region_relative_time_converter.from(frames); } +double +MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const +{ + return _region_relative_time_converter_double.from(frames); +} + void MidiRegionView::begin_resizing (bool /*at_front*/) { @@ -2572,12 +2710,12 @@ MidiRegionView::begin_resizing (bool /*at_front*/) resize_data->note = note; // create a new SimpleRect from the note which will be the resize preview - ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group, + ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group, ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1())); // calculate the colors: get the color settings uint32_t fill_color = UINT_RGBA_CHANGE_A( - ARDOUR_UI::config()->color ("midi note selected"), + UIConfiguration::instance().color ("midi note selected"), 128); // make the resize preview notes more transparent and bright @@ -2590,7 +2728,7 @@ MidiRegionView::begin_resizing (bool /*at_front*/) 0.85)); resize_rect->set_outline_color (NoteBase::calculate_outline ( - ARDOUR_UI::config()->color ("midi note selected"))); + UIConfiguration::instance().color ("midi note selected"))); resize_data->resize_rect = resize_rect; _resize_data.push_back(resize_data); @@ -2606,11 +2744,15 @@ MidiRegionView::begin_resizing (bool /*at_front*/) * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point * as the \a primary note. + * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode. + * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used. */ void -MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative) +MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { + TempoMap& tmap (trackview.session()->tempo_map()); bool cursor_set = false; + bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic; for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect; @@ -2619,15 +2761,15 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ if (at_front) { if (relative) { - current_x = canvas_note->x0() + delta_x; + current_x = canvas_note->x0() + delta_x + snap_delta; } else { - current_x = primary->x0() + delta_x; + current_x = primary->x0() + delta_x + snap_delta; } } else { if (relative) { - current_x = canvas_note->x1() + delta_x; + current_x = canvas_note->x1() + delta_x + snap_delta; } else { - current_x = primary->x1() + delta_x; + current_x = primary->x1() + delta_x + snap_delta; } } @@ -2641,29 +2783,62 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ } if (at_front) { - resize_rect->set_x0 (snap_to_pixel(current_x)); + if (with_snap) { + resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta); + } else { + resize_rect->set_x0 (current_x - snap_delta); + } resize_rect->set_x1 (canvas_note->x1()); } else { - resize_rect->set_x1 (snap_to_pixel(current_x)); + if (with_snap) { + resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta); + } else { + resize_rect->set_x1 (current_x - snap_delta); + } resize_rect->set_x0 (canvas_note->x0()); } if (!cursor_set) { - 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(); + /* Convert snap delta from pixels to beats. */ + framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta); + double snap_delta_beats = 0.0; + int sign = 1; + + /* negative beat offsets aren't allowed */ + if (snap_delta_samps > 0) { + snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps); + } else if (snap_delta_samps < 0) { + snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps); + sign = -1; + } + + double snapped_x; + uint32_t divisions = 0; + + if (with_snap) { + snapped_x = snap_pixel_to_sample (current_x, ensure_snap); + divisions = trackview.editor().get_grid_music_divisions (0); + } else { + snapped_x = trackview.editor ().pixel_to_sample (current_x); + } + const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions) + - midi_region()->beat()) + midi_region()->start_beats(); + + 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()->time() - beats + (sign * snap_delta_beats); len += canvas_note->note()->length(); } } else { if (beats >= canvas_note->note()->time()) { - len = beats - canvas_note->note()->time(); + len = beats - canvas_note->note()->time() - (sign * snap_delta_beats); } } + len = std::max(Evoral::Beats(1 / 512.0), len); + char buf[16]; snprintf (buf, sizeof (buf), "%.3g beats", len.to_double()); show_verbose_cursor (buf, 0, 0); @@ -2679,9 +2854,13 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ * Parameters the same as for \a update_resizing(). */ void -MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative) +MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { - start_note_diff_command (_("resize notes")); + _note_diff_command = _model->new_note_diff_command (_("resize notes")); + TempoMap& tmap (trackview.session()->tempo_map()); + + /* XX why doesn't snap_pixel_to_sample() handle this properly? */ + bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic; for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { Note* canvas_note = (*i)->note; @@ -2690,20 +2869,20 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ /* Get the new x position for this resize, which is in pixels relative * to the region position. */ - + double current_x; if (at_front) { if (relative) { - current_x = canvas_note->x0() + delta_x; + current_x = canvas_note->x0() + delta_x + snap_delta; } else { - current_x = primary->x0() + delta_x; + current_x = primary->x0() + delta_x + snap_delta; } } else { if (relative) { - current_x = canvas_note->x1() + delta_x; + current_x = canvas_note->x1() + delta_x + snap_delta; } else { - current_x = primary->x1() + delta_x; + current_x = primary->x1() + delta_x + snap_delta; } } @@ -2714,16 +2893,35 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ 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 (); + /* Convert snap delta from pixels to beats with sign. */ + framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta); + double snap_delta_beats = 0.0; + int sign = 1; + + if (snap_delta_samps > 0) { + snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps); + } else if (snap_delta_samps < 0) { + snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps); + sign = -1; + } + + uint32_t divisions = 0; + /* Convert the new x position to a frame within the source */ + framepos_t current_fr; + if (with_snap) { + current_fr = snap_pixel_to_sample (current_x, ensure_snap); + divisions = trackview.editor().get_grid_music_divisions (0); + } else { + current_fr = trackview.editor().pixel_to_sample (current_x); + } /* and then to beats */ - const Evoral::MusicalTime x_beats = region_frames_to_region_beats (current_x); + const Evoral::Beats x_beats = Evoral::Beats (tmap.exact_beat_at_frame (current_fr + midi_region()->position(), divisions) + - midi_region()->beat()) + midi_region()->start_beats(); if (at_front && x_beats < canvas_note->note()->end_time()) { - note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats); - - Evoral::MusicalTime len = canvas_note->note()->time() - x_beats; + note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats)); + Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats); len += canvas_note->note()->length(); if (!!len) { @@ -2732,12 +2930,9 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ } if (!at_front) { - const Evoral::MusicalTime len = x_beats - canvas_note->note()->time(); - - if (!!len) { - /* XXX convert to beats */ - note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); - } + Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0), + x_beats - canvas_note->note()->time() - (sign * snap_delta_beats)); + note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); } delete resize_rect; @@ -2745,7 +2940,7 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ } _resize_data.clear(); - apply_diff(); + apply_diff(true); } void @@ -2792,12 +2987,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; - Evoral::MusicalTime new_length; + Evoral::Beats new_start; + Evoral::Beats new_length; /* NOTE: the semantics of the two delta arguments are slightly subtle: @@ -2812,7 +3007,7 @@ MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evo if (front_delta < 0) { if (event->note()->time() < -front_delta) { - new_start = Evoral::MusicalTime(); + new_start = Evoral::Beats(); } else { new_start = event->note()->time() + front_delta; // moves earlier } @@ -2827,7 +3022,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; @@ -2886,14 +3081,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 = Evoral::MusicalTime(); + new_time = Evoral::Beats(); } else { new_time = event->note()->time() + delta; } @@ -2908,7 +3103,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); } @@ -3020,11 +3215,11 @@ 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) { if (fine) { - delta = Evoral::MusicalTime(1.0/128.0); + delta = Evoral::Beats(1.0/128.0); } else { /* grab the current grid distance */ delta = get_grid_beats(_region->position()); @@ -3044,8 +3239,8 @@ MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTim /* note the negation of the delta for start */ trim_note (*i, - (start ? -delta : Evoral::MusicalTime()), - (end ? delta : Evoral::MusicalTime())); + (start ? -delta : Evoral::Beats()), + (end ? delta : Evoral::Beats())); i = next; } @@ -3065,13 +3260,13 @@ MidiRegionView::nudge_notes (bool forward, bool fine) into a vector and sort before using the first one. */ - const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time()); - Evoral::MusicalTime delta; + const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time()); + Evoral::Beats delta; if (!fine) { /* non-fine, move by 1 bar regardless of snap */ - delta = Evoral::MusicalTime(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar()); + delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar()); } else if (trackview.editor().snap_mode() == Editing::SnapOff) { @@ -3138,13 +3333,22 @@ MidiRegionView::change_channel(uint8_t channel) void MidiRegionView::note_entered(NoteBase* ev) { + _note_entered = true; + Editor* editor = dynamic_cast(&trackview.editor()); if (_mouse_state == SelectTouchDragging) { + note_selected (ev, true); + } else if (editor->current_mouse_mode() == MouseContent) { + + remove_ghost_note (); show_verbose_cursor (ev->note ()); + } else if (editor->current_mouse_mode() == MouseDraw) { + + remove_ghost_note (); show_verbose_cursor (ev->note ()); } } @@ -3152,22 +3356,21 @@ MidiRegionView::note_entered(NoteBase* ev) void MidiRegionView::note_left (NoteBase*) { - Editor* editor = dynamic_cast(&trackview.editor()); + _note_entered = false; for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { (*i)->hide_velocity (); } - editor->verbose_cursor()->hide (); + hide_verbose_cursor (); } void MidiRegionView::patch_entered (PatchChange* p) { ostringstream s; - /* XXX should get patch name if we can */ - s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n' - << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n' + s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n' + << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n' << _("Channel ") << ((int) p->patch()->channel() + 1); show_verbose_cursor (s.str(), 10, 20); p->item().grab_focus(); @@ -3176,7 +3379,7 @@ MidiRegionView::patch_entered (PatchChange* p) void MidiRegionView::patch_left (PatchChange *) { - trackview.editor().verbose_cursor()->hide (); + hide_verbose_cursor (); /* focus will transfer back via the enter-notify event sent to this * midi region view. */ @@ -3196,7 +3399,7 @@ MidiRegionView::sysex_entered (SysEx* p) void MidiRegionView::sysex_left (SysEx *) { - trackview.editor().verbose_cursor()->hide (); + hide_verbose_cursor (); /* focus will transfer back via the enter-notify event sent to this * midi region view. */ @@ -3228,11 +3431,12 @@ MidiRegionView::get_fill_color() const trackview.editor().internal_editing() ? "editable region" : "midi frame base"); if (_selected) { - return ARDOUR_UI::config()->color_mod ("selected region base", mod_name); - } else if (high_enough_for_name || !ARDOUR_UI::config()->get_color_regions_using_track_color()) { - return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name); + return UIConfiguration::instance().color_mod ("selected region base", mod_name); + } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) && + !UIConfiguration::instance().get_color_regions_using_track_color()) { + return UIConfiguration::instance().color_mod ("midi frame base", mod_name); } - return ARDOUR_UI::config()->color_mod (fill_color, mod_name); + return UIConfiguration::instance().color_mod (fill_color, mod_name); } void @@ -3320,12 +3524,14 @@ MidiRegionView::selection_as_cut_buffer () const /** This method handles undo */ bool -MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx) +MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num) { + bool commit = false; // 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(); + if (!(*m)->empty()) { commit = true; } paste_internal(pos, ctx.count, ctx.times, **m); } @@ -3333,9 +3539,14 @@ MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContex 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); + if (a->second->paste(pos, selection, ctx, sub_num)) { + commit = true; + } } + if (commit) { + trackview.editor().commit_reversible_command (); + } return true; } @@ -3349,14 +3560,14 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float 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(); + 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, @@ -3364,7 +3575,7 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time duration, pos, _region->position(), pos_beats)); - clear_selection (); + clear_editor_note_selection (); for (int n = 0; n < (int) times; ++n) { @@ -3372,6 +3583,7 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time boost::shared_ptr copied_note (new NoteType (*((*i).get()))); copied_note->set_time (pos_beats + copied_note->time() - first_time); + copied_note->set_id (Evoral::next_event_id()); /* make all newly added notes selected */ @@ -3390,7 +3602,8 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float time DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame)); _region->clear_changes (); - _region->set_length (end_frame - _region->position()); + /* we probably need to get the snap modifier somehow to make this correct for non-musical use */ + _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0)); trackview.session()->add_command (new StatefulDiffCommand (_region)); } @@ -3524,25 +3737,22 @@ MidiRegionView::update_ghost_note (double x, double y) _note_group->canvas_to_item (x, y); PublicEditor& editor = trackview.editor (); - + 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 - */ - - const Evoral::MusicalTime length = get_grid_beats(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 ())); - /* 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 ( - std::max(Evoral::MusicalTime(), - 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); @@ -3556,7 +3766,11 @@ 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); @@ -3572,6 +3786,16 @@ MidiRegionView::remove_ghost_note () _ghost_note = 0; } +void +MidiRegionView::hide_verbose_cursor () +{ + trackview.editor().verbose_cursor()->hide (); + MidiTimeAxisView* mtv = dynamic_cast(&trackview); + if (mtv) { + mtv->set_note_highlight (NO_MIDI_NOTE); + } +} + void MidiRegionView::snap_changed () { @@ -3600,9 +3824,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; } @@ -3641,13 +3865,10 @@ void MidiRegionView::enable_display (bool yn) { RegionView::enable_display (yn); - if (yn) { - redisplay_model (); - } } void -MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos) +MidiRegionView::show_step_edit_cursor (Evoral::Beats pos) { if (_step_edit_cursor == 0) { ArdourCanvas::Item* const group = get_canvas_group(); @@ -3664,7 +3885,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; @@ -3684,12 +3905,14 @@ 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; if (_step_edit_cursor) { - _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_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 (_step_edit_cursor_position + beats) + - region_beats_to_region_frames (_step_edit_cursor_position))); } } @@ -3728,11 +3951,12 @@ MidiRegionView::data_recorded (boost::weak_ptr w) } /* convert from session frames to source beats */ - Evoral::MusicalTime const time_beats = _source_relative_time_converter.from(ev.time()); + 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, Evoral::MusicalTime(), ev.note(), ev.velocity())); + new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity())); add_note (note, true); @@ -3775,6 +3999,10 @@ MidiRegionView::trim_front_ending () /* Trim drag made start time -ve; fix this */ midi_region()->fix_negative_start (); } + /* until _start is modified on the fly during front trim, + we have to redisplay the model when a start trim has finished. + */ + enable_display (true); } void @@ -3810,14 +4038,13 @@ MidiRegionView::delete_sysex (SysEx* /*sysex*/) // display_sysexes(); } -void -MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const +std::string +MidiRegionView::get_note_name (boost::shared_ptr n, uint8_t note_value) const { using namespace MIDI::Name; - std::string name; - MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + MidiTimeAxisView* mtv = dynamic_cast(&trackview); if (mtv) { boost::shared_ptr device_names(mtv->get_device_names()); if (device_names) { @@ -3827,18 +4054,36 @@ MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const n->channel(), patch_key.bank(), patch_key.program(), - n->note()); + note_value); } } char buf[128]; snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d", - (int) n->note (), - name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(), + (int) note_value, + name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(), (int) n->channel() + 1, (int) n->velocity()); - show_verbose_cursor(buf, 10, 20); + return buf; +} + +void +MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr current_note, + uint8_t new_value) const +{ + MidiTimeAxisView* mtv = dynamic_cast(&trackview); + if (mtv) { + mtv->set_note_highlight (new_value); + } + + show_verbose_cursor(get_note_name(current_note, new_value), 10, 20); +} + +void +MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const +{ + show_verbose_cursor_for_new_note_value(n, n->note()); } void @@ -3849,6 +4094,33 @@ MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset)); } +uint8_t +MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const +{ + if (_model->notes().empty()) { + return 0x40; // No notes, use default + } + + 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(); + } + + // Interpolate velocity of surrounding notes + MidiModel::Notes::const_iterator n = m; + --n; + + const double frac = ((time - (*n)->time()).to_double() / + ((*m)->time() - (*n)->time()).to_double()); + + return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity())); +} + /** @param p A session framepos. * @param grid_frames Filled in with the number of frames that a grid interval is at p. * @return p snapped to the grid subdivision underneath it. @@ -3857,10 +4129,10 @@ framepos_t MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const { PublicEditor& editor = trackview.editor (); - - const Evoral::MusicalTime grid_beats = get_grid_beats(p); + const Evoral::Beats grid_beats = get_grid_beats(p); + const Evoral::Beats p_beat = max (Evoral::Beats(), region_frames_to_region_beats (p)); - grid_frames = region_beats_to_region_frames (grid_beats); + grid_frames = region_beats_to_region_frames (p_beat + grid_beats) - region_beats_to_region_frames (p_beat); /* Hack so that we always snap to the note that we are over, instead of snapping to the next one if we're more than halfway through the one we're over. @@ -3872,26 +4144,6 @@ MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_fr return snap_frame_to_frame (p); } -/** Called when the selection has been cleared in any MidiRegionView. - * @param rv MidiRegionView that the selection was cleared in. - */ -void -MidiRegionView::selection_cleared (MidiRegionView* rv) -{ - if (rv == this) { - return; - } - - /* Clear our selection in sympathy; but don't signal the fact */ - clear_selection (false); -} - -void -MidiRegionView::note_button_release () -{ - _note_player.reset(); -} - ChannelMode MidiRegionView::get_channel_mode () const { @@ -3907,14 +4159,14 @@ MidiRegionView::get_selected_channels () const } -Evoral::MusicalTime +Evoral::Beats 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); + PublicEditor& editor = trackview.editor(); + bool success = false; + Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos); if (!success) { - beats = Evoral::MusicalTime(1); + beats = Evoral::Beats(1); } return beats; }