X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=6686abb9b51481c958a259a2074204fbfbb794b8;hb=0092442a64f4216707b8368e895fc8a353c05da1;hp=012f49aa3603819f4a33d4cfe162f32dbf2bc3c9;hpb=76237a696cee8125102c161a210a22fdf484fc3b;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 012f49aa36..6686abb9b5 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -41,7 +41,7 @@ #include "ardour/session.h" #include "evoral/Parameter.hpp" -#include "evoral/MIDIEvent.hpp" +#include "evoral/Event.hpp" #include "evoral/Control.hpp" #include "evoral/midi_util.h" @@ -82,7 +82,7 @@ #include "sys_ex.h" #include "ui_config.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace ARDOUR; using namespace PBD; @@ -90,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, @@ -113,10 +111,8 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) , _channel_selection_scoped_note (0) - , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) - , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) @@ -125,20 +121,19 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) - , _note_entered (false) + , _entered_note (0) , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); + + _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline"); + _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill"); + _note_group->raise_to_top(); PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); 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, @@ -162,10 +157,8 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) , _channel_selection_scoped_note (0) - , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) - , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) @@ -174,20 +167,19 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) - , _note_entered (false) + , _entered_note (0) , _mouse_changed_selection (false) { CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name())); + + _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline"); + _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill"); + _note_group->raise_to_top(); PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); connect_to_diskstream (); - - SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); - - 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 @@ -197,6 +189,8 @@ MidiRegionView::parameter_changed (std::string const & p) if (_enable_display) { redisplay_model(); } + } else if (p == "color-regions-using-track-color") { + set_colors (); } } @@ -216,10 +210,8 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _step_edit_cursor_width (1.0) , _step_edit_cursor_position (0.0) , _channel_selection_scoped_note (0) - , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) - , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) , _no_sound_notes (false) @@ -228,7 +220,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _last_event_y (0) , _grabbed_keyboard (false) , _entered (false) - , _note_entered (false) + , _entered_note (0) , _mouse_changed_selection (false) { init (false); @@ -249,10 +241,8 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); + UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed)); 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& @@ -412,7 +398,7 @@ MidiRegionView::canvas_group_event(GdkEvent* ev) bool MidiRegionView::enter_notify (GdkEventCrossing* ev) { - enter_internal(); + enter_internal (ev->state); _entered = true; return false; @@ -434,26 +420,35 @@ 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 _entered_note 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 (!_entered_note) { + hide_verbose_cursor (); + } + } } } void -MidiRegionView::enter_internal() +MidiRegionView::enter_internal (uint32_t state) { if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) { // Show ghost note under pencil - create_ghost_note(_last_event_x, _last_event_y); - } - else { - remove_ghost_note (); - hide_verbose_cursor (); + create_ghost_note(_last_event_x, _last_event_y, state); } if (!_selection.empty()) { @@ -476,6 +471,7 @@ MidiRegionView::leave_internal() { hide_verbose_cursor (); remove_ghost_note (); + _entered_note = 0; if (_grabbed_keyboard) { Keyboard::magic_widget_drop_focus(); @@ -508,7 +504,21 @@ MidiRegionView::button_press (GdkEventButton* ev) if (_mouse_state != SelectTouchDragging) { _pressed_button = ev->button; - _mouse_state = Pressed; + + if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) { + + if (midi_view()->note_mode() == Percussive) { + editor->drags()->set (new HitCreateDrag (dynamic_cast (editor), group, this), (GdkEvent *) ev); + } else { + editor->drags()->set (new NoteCreateDrag (dynamic_cast (editor), group, this), (GdkEvent *) ev); + } + + _mouse_state = AddDragging; + remove_ghost_note (); + hide_verbose_cursor (); + } else { + _mouse_state = Pressed; + } return true; } @@ -543,8 +553,8 @@ 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; @@ -552,29 +562,13 @@ MidiRegionView::button_release (GdkEventButton* ev) case MouseTimeFX: { _mouse_changed_selection = true; - - if (Keyboard::is_insert_note_event(ev)) { - - double event_x, event_y; - - event_x = ev->x; - event_y = ev->y; - group->canvas_to_item (event_x, event_y); - - 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_selection (); - } + clear_editor_note_selection (); break; } case MouseDraw: - { - 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; - } + break; + default: break; } @@ -583,8 +577,8 @@ MidiRegionView::button_release (GdkEventButton* ev) break; 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); + /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion. + we don't want one when we were drag-selecting either. */ case SelectRectDragging: editor.drags()->end_grab ((GdkEvent *) ev); _mouse_state = None; @@ -608,33 +602,37 @@ MidiRegionView::motion (GdkEventMotion* ev) { PublicEditor& editor = trackview.editor (); - if (_note_entered) { + if (!_entered_note) { - remove_ghost_note (); + if (_mouse_state == AddDragging) { + if (_ghost_note) { + remove_ghost_note (); + } - } else 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()) && + _mouse_state != AddDragging) { - create_ghost_note (ev->x, ev->y); + create_ghost_note (ev->x, ev->y, ev->state); - } 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 && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { - update_ghost_note (ev->x, ev->y); + update_ghost_note (ev->x, ev->y, ev->state); - } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) { + } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) { - remove_ghost_note (); - hide_verbose_cursor (); + remove_ghost_note (); + hide_verbose_cursor (); - } else if (editor.current_mouse_mode() == MouseDraw) { + } else if (editor.current_mouse_mode() == MouseDraw) { - if (_ghost_note) { - update_ghost_note (ev->x, ev->y); - } - else { - create_ghost_note (ev->x, ev->y); + if (_ghost_note) { + update_ghost_note (ev->x, ev->y, ev->state); + } + else { + create_ghost_note (ev->x, ev->y, ev->state); + } } } @@ -651,16 +649,10 @@ MidiRegionView::motion (GdkEventMotion* ev) 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 (); - hide_verbose_cursor (); - return true; - } else if (m == MouseContent) { + if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { 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; @@ -735,15 +727,18 @@ MidiRegionView::key_press (GdkEventKey* ev) detectable auto-repeat is the name of the game and only sends repeated presses, carry out key actions at key press, not release. */ - bool unmodified = Keyboard::no_modifier_keys_pressed (ev); if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) { - _mouse_state = SelectTouchDragging; + + if (_mouse_state != AddDragging) { + _mouse_state = SelectTouchDragging; + } + return true; } else if (ev->keyval == GDK_Escape && unmodified) { - clear_selection(); + clear_editor_note_selection (); _mouse_state = None; } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) { @@ -928,7 +923,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, Evoral::Beats length, bool snap_t) +MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap) { if (length < 2 * DBL_EPSILON) { return; @@ -936,15 +931,15 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo MidiTimeAxisView* const mtv = dynamic_cast(&trackview); MidiStreamView* const view = mtv->midi_view(); + boost::shared_ptr mr = boost::dynamic_pointer_cast (_region); - // Start of note in frames relative to region start - if (snap_t) { - framecnt_t grid_frames; - t = snap_frame_to_grid_underneath (t, grid_frames); + if (!mr) { + return; } - const MidiModel::TimeType beat_time = region_frames_to_region_beats( - t + _region->start()); + // Start of note in frames relative to region start + const int32_t divisions = trackview.editor().get_grid_music_divisions (state); + Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap); const double note = view->y_to_note(y); const uint8_t chan = mtv->get_channel_for_add(); @@ -961,7 +956,7 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo start_note_diff_command(_("add note")); - clear_selection (); + clear_editor_note_selection (); note_diff_add_note (new_note, true, false); apply_diff(); @@ -970,9 +965,10 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo } 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) { @@ -981,10 +977,8 @@ MidiRegionView::clear_events (bool with_selection_signal) } } - for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - delete *i; - } + _note_group->clear (true); _events.clear(); _patch_changes.clear(); _sys_exes.clear(); @@ -999,7 +993,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 (false); + clear_events(); if (_enable_display) { redisplay_model(); @@ -1058,7 +1052,7 @@ MidiRegionView::note_diff_add_change (NoteBase* ev, } void -MidiRegionView::apply_diff (bool as_subcommand) +MidiRegionView::apply_diff (bool as_subcommand, bool was_copy) { bool add_or_remove; bool commit = false; @@ -1067,15 +1061,14 @@ MidiRegionView::apply_diff (bool as_subcommand) return; } - if ((add_or_remove = _note_diff_command->adds_or_removes())) { + if (!was_copy && (add_or_remove = _note_diff_command->adds_or_removes())) { // Mark all selected notes for selection when model reloads for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { _marked_for_selection.insert((*i)->note()); } } - midi_view()->midi_track()->midi_playlist()->region_edited( - _region, _note_diff_command); + 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); @@ -1101,24 +1094,24 @@ MidiRegionView::abort_command() { delete _note_diff_command; _note_diff_command = 0; - clear_selection(); + clear_editor_note_selection(); } NoteBase* MidiRegionView::find_canvas_note (boost::shared_ptr note) { + if (_optimization_iterator != _events.end()) { ++_optimization_iterator; } - if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) { - return *_optimization_iterator; + if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) { + return _optimization_iterator->second; } - for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) { - if ((*_optimization_iterator)->note() == note) { - return *_optimization_iterator; - } + _optimization_iterator = _events.find (note); + if (_optimization_iterator != _events.end()) { + return _optimization_iterator->second; } return 0; @@ -1126,19 +1119,31 @@ MidiRegionView::find_canvas_note (boost::shared_ptr note) /** This version finds any canvas note matching the supplied note. */ NoteBase* -MidiRegionView::find_canvas_note (NoteType note) +MidiRegionView::find_canvas_note (Evoral::event_id_t id) { Events::iterator it; for (it = _events.begin(); it != _events.end(); ++it) { - if (*((*it)->note()) == note) { - return *it; + if (it->first->id() == id) { + return it->second; } } return 0; } +boost::shared_ptr +MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p) +{ + PatchChanges::const_iterator f = _patch_changes.find (p); + + if (f != _patch_changes.end()) { + return f->second; + } + + return boost::shared_ptr(); +} + void MidiRegionView::get_events (Events& e, Evoral::Sequence::NoteOperator op, uint8_t val, int chan_mask) { @@ -1148,7 +1153,7 @@ MidiRegionView::get_events (Events& e, Evoral::Sequence::NoteOper for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { NoteBase* cne = find_canvas_note (*n); if (cne) { - e.push_back (cne); + e.insert (make_pair (*n, cne)); } } } @@ -1164,8 +1169,8 @@ MidiRegionView::redisplay_model() 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); + if (i->second->note()->length() > 0) { + update_note(i->second); } } _last_display_zoom = zoom; @@ -1177,80 +1182,113 @@ MidiRegionView::redisplay_model() return; } - for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - (*i)->invalidate (); + for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) { + _optimization_iterator->second->invalidate(); } - MidiModel::ReadLock lock(_model->read_lock()); - - MidiModel::Notes& notes (_model->notes()); + bool empty_when_starting = _events.empty(); _optimization_iterator = _events.begin(); + MidiModel::Notes missing_notes; + Note* sus = NULL; + Hit* hit = NULL; - bool empty_when_starting = _events.empty(); + MidiModel::ReadLock lock(_model->read_lock()); + MidiModel::Notes& notes (_model->notes()); + 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 (); - update_note (cne); - if (visible) { cne->show (); } else { cne->hide (); } - } else { - - 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 (); + missing_notes.insert (note); } } } - /* remove note items that are no longer valid */ - if (!empty_when_starting) { + MidiModel::Notes::iterator f; for (Events::iterator i = _events.begin(); i != _events.end(); ) { - if (!(*i)->valid ()) { + + NoteBase* cne = i->second; + + /* remove note items that are no longer valid */ + if (!cne->valid()) { for (vector::iterator j = ghosts.begin(); j != ghosts.end(); ++j) { MidiGhostRegion* gr = dynamic_cast (*j); if (gr) { - gr->remove_note (*i); + gr->remove_note (cne); } } - delete *i; + delete cne; i = _events.erase (i); } else { + bool visible = cne->item()->visible(); + + if ((sus = dynamic_cast(cne))) { + + if (visible) { + update_sustained (sus); + } + + for (std::vector::iterator i = ghosts.begin(); i != ghosts.end(); ++i) { + MidiGhostRegion* gr = dynamic_cast (*i); + if (gr) { + gr->update_note (sus, !visible || gr->trackview.hidden()); + } + } + } else if ((hit = dynamic_cast(cne))) { + + if (visible) { + update_hit (hit); + } + + for (std::vector::iterator i = ghosts.begin(); i != ghosts.end(); ++i) { + MidiGhostRegion* gr = dynamic_cast (*i); + if (gr) { + gr->update_hit (hit, !visible || gr->trackview.hidden()); + } + } + } ++i; } } } - _patch_changes.clear(); + for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) { + boost::shared_ptr note (*n); + NoteBase* cne; + bool visible; + + if (note_in_region_range (note, visible)) { + if (visible) { + cne = add_note (note, true); + } else { + cne = add_note (note, false); + } + } else { + cne = add_note (note, false); + } + + for (set::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) { + if ((*it) == note->id()) { + add_to_selection (cne); + } + } + } + _sys_exes.clear(); display_sysexes(); @@ -1260,12 +1298,6 @@ MidiRegionView::redisplay_model() _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 - make a note that a sort is required for those cases that require it. - */ - - _sort_needed = true; } void @@ -1286,13 +1318,31 @@ void MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel) { for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) { + boost::shared_ptr p; if ((*i)->channel() != channel) { continue; } - const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel); - add_canvas_patch_change (*i, patch_name, active_channel); + if ((p = find_canvas_patch_change (*i)) != 0) { + + const framecnt_t region_frames = source_beats_to_region_frames ((*i)->time()); + + if (region_frames < 0 || region_frames >= _region->length()) { + p->hide(); + } else { + const double x = trackview.editor().sample_to_pixel (region_frames); + const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel); + p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0)); + p->flag()->set_text (patch_name); + + p->show(); + } + + } else { + const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel); + add_canvas_patch_change (*i, patch_name, active_channel); + } } } @@ -1305,14 +1355,9 @@ MidiRegionView::display_sysexes() 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); - - if (mev) { - if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { - have_periodic_system_messages = true; - break; - } + if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) { + have_periodic_system_messages = true; + break; } } @@ -1337,17 +1382,11 @@ 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); - Evoral::Beats time = (*i)->time(); - if (mev) { - if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { - if (!display_periodic_messages) { - continue; - } + if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) { + if (!display_periodic_messages) { + continue; } } @@ -1395,36 +1434,38 @@ MidiRegionView::~MidiRegionView () if (_active_notes) { end_write(); } - - _selection_cleared_connection.disconnect (); - - _selection.clear(); - clear_events (false); + _entered_note = 0; + clear_events (); delete _note_group; delete _note_diff_command; delete _step_edit_cursor; - delete _temporary_note_group; } 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()); _region_relative_time_converter_double.set_origin_b(_region->position()); - set_duration(_region->length(), 0); - if (_enable_display) { - redisplay_model(); - } + /* 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 end and start trim so we can update the view*/ + if (!what_changed.contains (ARDOUR::Properties::start) && + what_changed.contains (ARDOUR::Properties::length)) { + enable_display (true); + } else if (what_changed.contains (ARDOUR::Properties::start) && + what_changed.contains (ARDOUR::Properties::length)) { + enable_display (true); + } } void @@ -1436,11 +1477,15 @@ MidiRegionView::reset_width_dependent_items (double pixel_width) redisplay_model(); } - for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { - if ((*x)->canvas_item()->width() >= _pixel_width) { - (*x)->hide(); - } else { - (*x)->show(); + bool hide_all = false; + PatchChanges::iterator x = _patch_changes.begin(); + if (x != _patch_changes.end()) { + hide_all = x->second->flag()->width() >= _pixel_width; + } + + if (hide_all) { + for (; x != _patch_changes.end(); ++x) { + x->second->hide(); } } @@ -1463,7 +1508,7 @@ MidiRegionView::set_height (double height) } for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { - (*x)->set_height (midi_stream_view()->contents_height()); + (*x).second->set_height (midi_stream_view()->contents_height()); } if (_step_edit_cursor) { @@ -1489,44 +1534,7 @@ MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force) _current_range_min = min; _current_range_max = max; - for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) { - NoteBase* event = *i; - boost::shared_ptr note (event->note()); - - if (note->note() < _current_range_min || - note->note() > _current_range_max) { - event->hide(); - } else { - event->show(); - } - - if (Note* cnote = dynamic_cast(event)) { - - 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); - - } else if (Hit* chit = dynamic_cast(event)) { - update_hit (chit); - } - } + redisplay_model (); } GhostRegion* @@ -1545,14 +1553,16 @@ MidiRegionView::add_ghost (TimeAxisView& tv) ghost = new MidiGhostRegion (*this, tv, trackview, unit_position); } + ghost->set_colors (); + ghost->set_height (); + ghost->set_duration (_region->length() / samples_per_pixel); + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - ghost->add_note(*i); + ghost->add_note(i->second); } - ghost->set_height (); - ghost->set_duration (_region->length() / samples_per_pixel); ghosts.push_back (ghost); - + enable_display (true); return ghost; } @@ -1679,15 +1689,14 @@ MidiRegionView::start_playing_midi_chord (vector > n bool MidiRegionView::note_in_region_range (const boost::shared_ptr note, bool& visible) const { - /* This is imprecise due to all the conversion conversion involved, so only - hide notes if they seem to start more than one tick before the start. */ - const framecnt_t tick_frames = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate()); - const framepos_t note_start_frames = source_beats_to_region_frames (note->time()); - const bool outside = ((note_start_frames <= -tick_frames) || - (note_start_frames >= _region->length())); + const boost::shared_ptr midi_reg = midi_region(); + + /* must compare double explicitly as Beats::operator< rounds to ppqn */ + const bool outside = (note->time().to_double() < midi_reg->start_beats() || + note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats()); - visible = (note->note() >= midi_stream_view()->lowest_note()) && - (note->note() <= midi_stream_view()->highest_note()); + visible = (note->note() >= _current_range_min) && + (note->note() <= _current_range_max); return !outside; } @@ -1711,23 +1720,36 @@ MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions) void 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 double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note())); - ev->set_x0 (x); - ev->set_y0 (y0); + const double session_source_start = _region->quarter_note() - mr->start_beats(); + const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position(); + + const double x0 = trackview.editor().sample_to_pixel (note_start_frames); + double x1; + const double y0 = 1 + floor(note_to_y(note->note())); + double y1; /* trim note display to not overlap the end of its region */ + if (note->length().to_double() > 0.0) { + double note_end_time = note->end_time().to_double(); - if (note->length() > 0) { - const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length()); - ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1); + if (note_end_time > mr->start_beats() + mr->length_beats()) { + note_end_time = mr->start_beats() + mr->length_beats(); + } + + const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position(); + + x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1; } else { - ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1); + 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(note_height()) - 1); + + ev->set (ArdourCanvas::Rect (x0, y0, x1, y1)); if (!note->length()) { if (_active_notes && note->note() < 128) { @@ -1735,7 +1757,7 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions) if (old_rect) { /* There is an active note on this key, so we have a stuck note. Finish the old rectangle here. */ - old_rect->set_x1 (x); + old_rect->set_x1 (x1); old_rect->set_outline_all (); } _active_notes[note->note()] = ev; @@ -1751,17 +1773,10 @@ MidiRegionView::update_sustained (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())); - - 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); - } - } - } + 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())); + } void @@ -1769,10 +1784,12 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions) { boost::shared_ptr note = ev->note(); - const framepos_t note_start_frames = source_beats_to_region_frames(note->time()); + const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats()); + const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _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; + const double diamond_size = std::max(1., floor(note_height()) - 2.); + const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5; // see DnD note in MidiRegionView::apply_note_range() above if (y <= 0 || y >= _height) { @@ -1785,17 +1802,10 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions) 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); - } - } - } + 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())); + } /** Add a MIDI note to the view (with length). @@ -1811,7 +1821,7 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) if (midi_view()->note_mode() == Sustained) { - Note* ev_rect = new Note (*this, _note_group, note); + Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak update_sustained (ev_rect); @@ -1819,9 +1829,9 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) } else if (midi_view()->note_mode() == Percussive) { - const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.); + const double diamond_size = std::max(1., floor(note_height()) - 2.); - Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); + Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak update_hit (ev_diamond); @@ -1849,7 +1859,7 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) } event->on_channel_selection_change (get_selected_channels()); - _events.push_back(event); + _events.insert (make_pair (event->note(), event)); if (visible) { event->show(); @@ -1877,7 +1887,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); @@ -1889,7 +1900,7 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity start_note_diff_command (_("step add")); - clear_selection (); + clear_editor_note_selection (); note_diff_add_note (new_note, true, false); apply_diff(); @@ -1920,14 +1931,16 @@ MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const // so we need to do something more sophisticated to keep its color // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill) // up to date. - boost::shared_ptr patch_change = boost::shared_ptr( new PatchChange(*this, group, displaytext, height, x, 1.0, instrument_info(), - patch)); + patch, + _patch_change_outline, + _patch_change_fill) + ); if (patch_change->item().width() < _pixel_width) { // Show unless patch change is beyond the region bounds @@ -1940,7 +1953,19 @@ MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const patch_change->hide (); } - _patch_changes.push_back (patch_change); + _patch_changes.insert (make_pair (patch, patch_change)); +} + +void +MidiRegionView::remove_canvas_patch_change (PatchChange* pc) +{ + /* remove the canvas item */ + for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { + if (x->second->patch() == pc->patch()) { + _patch_changes.erase (x); + break; + } + } } MIDI::Name::PatchPrimaryKey @@ -1983,6 +2008,7 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri { 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()) { @@ -1997,7 +2023,7 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri _model->apply_command (*trackview.session(), c); trackview.editor().commit_reversible_command (); - _patch_changes.clear (); + remove_canvas_patch_change (&pc); display_patch_changes (); } @@ -2027,7 +2053,13 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const _model->apply_command (*trackview.session(), c); trackview.editor().commit_reversible_command (); - _patch_changes.clear (); + for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { + if (x->second->patch() == old_change) { + _patch_changes.erase (x); + break; + } + } + display_patch_changes (); } @@ -2055,7 +2087,6 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChangeapply_command (*trackview.session(), c); trackview.editor().commit_reversible_command (); - _patch_changes.clear (); display_patch_changes (); } @@ -2068,7 +2099,6 @@ MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t) _model->apply_command (*trackview.session(), c); trackview.editor().commit_reversible_command (); - _patch_changes.clear (); display_patch_changes (); } @@ -2076,12 +2106,13 @@ 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 (); + remove_canvas_patch_change (pc); display_patch_changes (); } @@ -2100,6 +2131,10 @@ MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta) void MidiRegionView::note_deleted (NoteBase* cne) { + if (_entered_note && cne == _entered_note) { + _entered_note = 0; + } + if (_selection.empty()) { return; } @@ -2114,6 +2149,10 @@ MidiRegionView::delete_selection() return; } + if (trackview.editor().drags()->active()) { + return; + } + start_note_diff_command (_("delete selection")); for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { @@ -2125,6 +2164,7 @@ MidiRegionView::delete_selection() _selection.clear(); apply_diff (); + hide_verbose_cursor (); } @@ -2139,80 +2179,65 @@ MidiRegionView::delete_note (boost::shared_ptr n) } void -MidiRegionView::clear_selection_except (NoteBase* ev, bool signal) +MidiRegionView::clear_editor_note_selection () { - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { - if ((*i) != ev) { - Selection::iterator tmp = i; - ++tmp; + DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n"); + PublicEditor& editor(trackview.editor()); + editor.get_selection().clear_midi_notes(); +} - (*i)->set_selected (false); - (*i)->hide_velocity (); - _selection.erase (i); +void +MidiRegionView::clear_selection () +{ + clear_selection_internal(); + PublicEditor& editor(trackview.editor()); + editor.get_selection().remove(this); +} - i = tmp; - } else { - ++i; - } +void +MidiRegionView::clear_selection_internal () +{ + DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n"); + + 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); + add_to_selection (i->second); } } 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()); + framepos_t t = source_beats_to_absolute_frames(i->first->time()); if (t >= start && t <= end) { - add_to_selection (*i); + add_to_selection (i->second); } } } @@ -2221,10 +2246,10 @@ void MidiRegionView::invert_selection () { for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - if ((*i)->selected()) { - remove_from_selection(*i); + if (i->second->selected()) { + remove_from_selection(i->second); } else { - add_to_selection (*i); + add_to_selection (i->second); } } } @@ -2233,13 +2258,13 @@ MidiRegionView::invert_selection () The requested notes most likely won't exist in the view until the next model redisplay. */ void -MidiRegionView::select_notes (list > notes) +MidiRegionView::select_notes (list notes) { NoteBase* cne; - list >::iterator n; + list::iterator n; for (n = notes.begin(); n != notes.end(); ++n) { - if ((cne = find_canvas_note(*(*n))) != 0) { + if ((cne = find_canvas_note(*n)) != 0) { add_to_selection (cne); } else { _pending_note_selection.insert(*n); @@ -2272,7 +2297,7 @@ MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, b } 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 @@ -2347,11 +2372,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) { @@ -2387,11 +2409,10 @@ MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend) /* find notes entirely within OR spanning the earliest..latest range */ - if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) || - ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) { - add_to_selection (*i); + if ((i->first->time() >= earliest && i->first->end_time() <= latest) || + (i->first->time() <= earliest && i->first->end_time() >= latest)) { + add_to_selection (i->second); } - } } } @@ -2420,14 +2441,14 @@ MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double g // We probably need a tree to be able to find events in O(log(n)) time. for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) { + if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) { // Rectangles intersect - if (!(*i)->selected()) { - add_to_selection (*i); + if (!i->second->selected()) { + add_to_selection (i->second); } - } else if ((*i)->selected() && !extend) { + } else if (i->second->selected() && !extend) { // Rectangles do not intersect - remove_from_selection (*i); + remove_from_selection (i->second); } } @@ -2462,13 +2483,13 @@ MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool exten // We probably need a tree to be able to find events in O(log(n)) time. for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) { + if ((i->second->y1() >= y1 && i->second->y1() <= y2)) { // within y- (note-) range - if (!(*i)->selected()) { - add_to_selection (*i); + if (!i->second->selected()) { + add_to_selection (i->second); } - } else if ((*i)->selected() && !extend) { - remove_from_selection (*i); + } else if (i->second->selected() && !extend) { + remove_from_selection (i->second); } } } @@ -2560,67 +2581,192 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy) } } +NoteBase* +MidiRegionView::copy_selection () +{ + NoteBase* note; + _copy_drag_events.clear (); + + if (_selection.empty()) { + return 0; + } + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + boost::shared_ptr g (new NoteType (*((*i)->note()))); + if (midi_view()->note_mode() == Sustained) { + Note* n = new Note (*this, _note_group, g); + update_sustained (n, false); + note = n; + } else { + Hit* h = new Hit (*this, _note_group, 10, g); + update_hit (h, false); + note = h; + } + + _copy_drag_events.push_back (note); + } + + return _copy_drag_events.front (); +} + void -MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) +MidiRegionView::move_copies (double dx, double dy, double cumulative_dy) +{ + typedef vector > PossibleChord; + PossibleChord to_play; + Evoral::Beats earliest = Evoral::MaxBeats; + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) { + if ((*i)->note()->time() < earliest) { + earliest = (*i)->note()->time(); + } + } + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) { + if ((*i)->note()->time() == earliest) { + to_play.push_back ((*i)->note()); + } + (*i)->move_event(dx, dy); + } + + if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) { + + if (to_play.size() > 1) { + + PossibleChord shifted; + + for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) { + boost::shared_ptr moved_note (new NoteType (**n)); + moved_note->set_note (moved_note->note() + cumulative_dy); + shifted.push_back (moved_note); + } + + start_playing_midi_chord (shifted); + + } else if (!to_play.empty()) { + + boost::shared_ptr moved_note (new NoteType (*to_play.front())); + moved_note->set_note (moved_note->note() + cumulative_dy); + start_playing_midi_note (moved_note); + } + } +} + +void +MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote, bool copy) { uint8_t lowest_note_in_selection = 127; uint8_t highest_note_in_selection = 0; uint8_t highest_note_difference = 0; - // find highest and lowest notes first + if (!copy) { + // find highest and lowest notes first - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - uint8_t pitch = (*i)->note()->note(); - lowest_note_in_selection = std::min(lowest_note_in_selection, pitch); - highest_note_in_selection = std::max(highest_note_in_selection, pitch); - } - - /* - cerr << "dnote: " << (int) dnote << endl; - cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note()) - << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl; - cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): " - << int(highest_note_in_selection) << endl; - cerr << "selection size: " << _selection.size() << endl; - cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl; - */ + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + uint8_t pitch = (*i)->note()->note(); + lowest_note_in_selection = std::min(lowest_note_in_selection, pitch); + highest_note_in_selection = std::max(highest_note_in_selection, pitch); + } - // Make sure the note pitch does not exceed the MIDI standard range - if (highest_note_in_selection + dnote > 127) { - highest_note_difference = highest_note_in_selection - 127; - } + /* + cerr << "dnote: " << (int) dnote << endl; + cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note()) + << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl; + cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): " + << int(highest_note_in_selection) << endl; + cerr << "selection size: " << _selection.size() << endl; + cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl; + */ - start_note_diff_command (_("move notes")); + // Make sure the note pitch does not exceed the MIDI standard range + if (highest_note_in_selection + dnote > 127) { + highest_note_difference = highest_note_in_selection - 127; + } + TempoMap& map (trackview.session()->tempo_map()); - for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { + start_note_diff_command (_("move notes")); - framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt; - Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames); + for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { - if (new_time < 0) { - continue; + double const start_qn = _region->quarter_note() - midi_region()->start_beats(); + framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt; + Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn); + if (new_time < 0) { + continue; + } + + note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time); + + uint8_t original_pitch = (*i)->note()->note(); + uint8_t new_pitch = original_pitch + dnote - highest_note_difference; + + // keep notes in standard midi range + clamp_to_0_127(new_pitch); + + lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); + highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + + note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch); + } + } else { + + clear_editor_note_selection (); + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) { + uint8_t pitch = (*i)->note()->note(); + lowest_note_in_selection = std::min(lowest_note_in_selection, pitch); + highest_note_in_selection = std::max(highest_note_in_selection, pitch); } - note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time); + // Make sure the note pitch does not exceed the MIDI standard range + if (highest_note_in_selection + dnote > 127) { + highest_note_difference = highest_note_in_selection - 127; + } + + TempoMap& map (trackview.session()->tempo_map()); + start_note_diff_command (_("copy notes")); + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) { + + /* update time */ + + double const start_qn = _region->quarter_note() - midi_region()->start_beats(); + framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt; + Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn); + + if (new_time < 0) { + continue; + } + + (*i)->note()->set_time (new_time); + + /* update pitch */ + + uint8_t original_pitch = (*i)->note()->note(); + uint8_t new_pitch = original_pitch + dnote - highest_note_difference; - uint8_t original_pitch = (*i)->note()->note(); - uint8_t new_pitch = original_pitch + dnote - highest_note_difference; + (*i)->note()->set_note (new_pitch); - // keep notes in standard midi range - clamp_to_0_127(new_pitch); + // keep notes in standard midi range + clamp_to_0_127(new_pitch); - lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); - highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); + highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + + note_diff_add_note ((*i)->note(), true); + + delete *i; + } - note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch); + _copy_drag_events.clear (); } - apply_diff(); + apply_diff (false, copy); // care about notes being moved beyond the upper/lower bounds on the canvas if (lowest_note_in_selection < midi_stream_view()->lowest_note() || highest_note_in_selection > midi_stream_view()->highest_note()) { - midi_stream_view()->set_note_range(MidiStreamView::ContentsRange); + midi_stream_view()->set_note_range (MidiStreamView::ContentsRange); } } @@ -2754,6 +2900,7 @@ MidiRegionView::begin_resizing (bool /*at_front*/) void 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; @@ -2815,9 +2962,19 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ sign = -1; } - const double snapped_x = (with_snap ? snap_pixel_to_sample (current_x, ensure_snap) : trackview.editor ().pixel_to_sample (current_x)); - Evoral::Beats beats = region_frames_to_region_beats (snapped_x); - Evoral::Beats len = Evoral::Beats(); + double snapped_x; + int32_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()) { @@ -2850,6 +3007,7 @@ void MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { _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; @@ -2897,16 +3055,20 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ 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) + _region->start (); + 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) + _region->start (); + current_fr = trackview.editor().pixel_to_sample (current_x); } /* and then to beats */ - const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr); + const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions); + const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats(); + const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start); if (at_front && x_beats < canvas_note->note()->end_time()) { note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats)); @@ -3255,7 +3417,7 @@ MidiRegionView::nudge_notes (bool forward, bool fine) if (!fine) { /* non-fine, move by 1 bar regardless of snap */ - delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar()); + delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar()); } else if (trackview.editor().snap_mode() == Editing::SnapOff) { @@ -3322,15 +3484,22 @@ MidiRegionView::change_channel(uint8_t channel) void MidiRegionView::note_entered(NoteBase* ev) { - _note_entered = true; + _entered_note = ev; 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 ()); } } @@ -3338,7 +3507,7 @@ MidiRegionView::note_entered(NoteBase* ev) void MidiRegionView::note_left (NoteBase*) { - _note_entered = false; + _entered_note = 0; for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { (*i)->hide_velocity (); @@ -3351,9 +3520,8 @@ 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' + << 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(); @@ -3371,7 +3539,7 @@ MidiRegionView::patch_left (PatchChange *) void MidiRegionView::sysex_entered (SysEx* p) { - ostringstream s; + // ostringstream s; // CAIROCANVAS // need a way to extract text from p->_flag->_text // s << p->text(); @@ -3435,7 +3603,7 @@ MidiRegionView::midi_channel_mode_changed () // Update notes for selection for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - (*i)->on_channel_selection_change (mask); + i->second->on_channel_selection_change (mask); } _patch_changes.clear (); @@ -3507,14 +3675,16 @@ 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; } + if (!(*m)->empty()) { + commit = true; + } paste_internal(pos, ctx.count, ctx.times, **m); } @@ -3522,7 +3692,10 @@ 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) { - if (a->second->paste(pos, selection, ctx)) { + if (a->second->paste(pos, selection, ctx, sub_num)) { + if(!commit) { + trackview.editor().begin_reversible_command (Operations::paste); + } commit = true; } } @@ -3549,23 +3722,23 @@ MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float 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; + const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset; Evoral::Beats end_point = Evoral::Beats(); DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n", first_time, last_time, duration, pos, _region->position(), - pos_beats)); + quarter_note)); - clear_selection (); + clear_editor_note_selection (); for (int n = 0; n < (int) times; ++n) { 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 (pos_beats + copied_note->time() - first_time); + copied_note->set_time (quarter_note + copied_note->time() - first_time); copied_note->set_id (Evoral::next_event_id()); /* make all newly added notes selected */ @@ -3585,7 +3758,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)); } @@ -3598,53 +3772,47 @@ struct EventNoteTimeEarlyFirstComparator { } }; -void -MidiRegionView::time_sort_events () -{ - if (!_sort_needed) { - return; - } - - EventNoteTimeEarlyFirstComparator cmp; - _events.sort (cmp); - - _sort_needed = false; -} - void MidiRegionView::goto_next_note (bool add_to_selection) { bool use_next = false; - if (_events.back()->selected()) { - return; - } - - time_sort_events (); - MidiTimeAxisView* const mtv = dynamic_cast(&trackview); uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask(); + NoteBase* first_note = 0; - for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - if ((*i)->selected()) { - use_next = true; - continue; - } else if (use_next) { - if (channel_mask & (1 << (*i)->note()->channel())) { - if (!add_to_selection) { - unique_select (*i); - } else { - note_selected (*i, true, false); + MidiModel::ReadLock lock(_model->read_lock()); + MidiModel::Notes& notes (_model->notes()); + + for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { + NoteBase* cne = 0; + if ((cne = find_canvas_note (*n))) { + + if (!first_note && (channel_mask & (1 << (*n)->channel()))) { + first_note = cne; + } + + if (cne->selected()) { + use_next = true; + continue; + } else if (use_next) { + if (channel_mask & (1 << (*n)->channel())) { + if (!add_to_selection) { + unique_select (cne); + } else { + note_selected (cne, true, false); + } + + return; } - return; } } } /* use the first one */ - if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) { - unique_select (_events.front()); + if (!_events.empty() && first_note) { + unique_select (first_note); } } @@ -3653,35 +3821,43 @@ MidiRegionView::goto_previous_note (bool add_to_selection) { bool use_next = false; - if (_events.front()->selected()) { - return; - } - - time_sort_events (); - MidiTimeAxisView* const mtv = dynamic_cast(&trackview); uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask (); + NoteBase* last_note = 0; - for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) { - if ((*i)->selected()) { - use_next = true; - continue; - } else if (use_next) { - if (channel_mask & (1 << (*i)->note()->channel())) { - if (!add_to_selection) { - unique_select (*i); - } else { - note_selected (*i, true, false); + MidiModel::ReadLock lock(_model->read_lock()); + MidiModel::Notes& notes (_model->notes()); + + for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) { + NoteBase* cne = 0; + if ((cne = find_canvas_note (*n))) { + + if (!last_note && (channel_mask & (1 << (*n)->channel()))) { + last_note = cne; + } + + if (cne->selected()) { + use_next = true; + continue; + + } else if (use_next) { + if (channel_mask & (1 << (*n)->channel())) { + if (!add_to_selection) { + unique_select (cne); + } else { + note_selected (cne, true, false); + } + + return; } - return; } } } /* use the last one */ - if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) { - unique_select (*(_events.rbegin())); + if (!_events.empty() && last_note) { + unique_select (last_note); } } @@ -3690,24 +3866,24 @@ MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_s { bool had_selected = false; - time_sort_events (); + /* we previously time sorted events here, but Notes is a multiset sorted by time */ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - if ((*i)->selected()) { - selected.insert ((*i)->note()); + if (i->second->selected()) { + selected.insert (i->first); had_selected = true; } } if (allow_all_if_none_selected && !had_selected) { for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - selected.insert ((*i)->note()); + selected.insert (i->first); } } } void -MidiRegionView::update_ghost_note (double x, double y) +MidiRegionView::update_ghost_note (double x, double y, uint32_t state) { x = std::max(0.0, x); @@ -3721,21 +3897,35 @@ MidiRegionView::update_ghost_note (double x, double 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); + + const int32_t divisions = editor.get_grid_music_divisions (state); + const bool shift_snap = midi_view()->note_mode() != Percussive; + const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap); + + /* prevent Percussive mode from displaying a ghost hit at region end */ + if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) { + _ghost_note->hide(); + hide_verbose_cursor (); + return; + } + + /* ghost note may have been snapped before region */ + if (_ghost_note && snapped_beats.to_double() < 0.0) { + _ghost_note->hide(); + return; + + } else if (_ghost_note) { + _ghost_note->show(); + } /* 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 ())); + const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position()); - _ghost_note->note()->set_time (time); + _ghost_note->note()->set_time (snapped_beats); _ghost_note->note()->set_length (length); - _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y)); + _ghost_note->note()->set_note (y_to_note (y)); _ghost_note->note()->set_channel (mtv->get_channel_for_add ()); - _ghost_note->note()->set_velocity (get_velocity_for_add (time)); - + _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats)); /* the ghost note does not appear in ghost regions, so pass false in here */ update_note (_ghost_note, false); @@ -3743,7 +3933,7 @@ MidiRegionView::update_ghost_note (double x, double y) } void -MidiRegionView::create_ghost_note (double x, double y) +MidiRegionView::create_ghost_note (double x, double y, uint32_t state) { remove_ghost_note (); @@ -3755,7 +3945,7 @@ MidiRegionView::create_ghost_note (double x, double y) } _ghost_note->set_ignore_events (true); _ghost_note->set_outline_color (0x000000aa); - update_ghost_note (x, y); + update_ghost_note (x, y, state); _ghost_note->show (); show_verbose_cursor (_ghost_note->note ()); @@ -3785,7 +3975,7 @@ MidiRegionView::snap_changed () return; } - create_ghost_note (_last_ghost_x, _last_ghost_y); + create_ghost_note (_last_ghost_x, _last_ghost_y, 0); } void @@ -3799,7 +3989,7 @@ MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, doub { /* XXX: This is dead code. What was it for? */ - double note = midi_stream_view()->y_to_note(y); + double note = y_to_note(y); Events e; MidiTimeAxisView* const mtv = dynamic_cast(&trackview); @@ -3820,8 +4010,8 @@ MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, doub } for (Events::iterator i = e.begin(); i != e.end(); ++i) { - if (_selection.insert (*i).second) { - (*i)->set_selected (true); + if (_selection.insert (i->second).second) { + i->second->set_selected (true); } } @@ -3836,8 +4026,11 @@ MidiRegionView::color_handler () { RegionView::color_handler (); + _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline"); + _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill"); + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - (*i)->set_selected ((*i)->selected()); // will change color + i->second->set_selected (i->second->selected()); // will change color } /* XXX probably more to do here */ @@ -3847,9 +4040,6 @@ void MidiRegionView::enable_display (bool yn) { RegionView::enable_display (yn); - if (yn) { - redisplay_model (); - } } void @@ -3895,7 +4085,9 @@ 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))); } } @@ -3923,7 +4115,7 @@ MidiRegionView::data_recorded (boost::weak_ptr w) framepos_t back = max_framepos; for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) { - Evoral::MIDIEvent const ev (*i, false); + const Evoral::Event& ev = *i; if (ev.is_channel_event()) { if (get_channel_mode() == FilterChannels) { @@ -3963,21 +4155,14 @@ MidiRegionView::data_recorded (boost::weak_ptr w) void 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. + /* We used to eparent the note group to the region view's parent, so that it didn't change. + now we update it. */ - _temporary_note_group = new ArdourCanvas::Container (group->parent ()); - _temporary_note_group->move (group->position ()); - _note_group->reparent (_temporary_note_group); } void MidiRegionView::trim_front_ending () { - _note_group->reparent (group); - delete _temporary_note_group; - _temporary_note_group = 0; - if (_region->start() < 0) { /* Trim drag made start time -ve; fix this */ midi_region()->fix_negative_start (); @@ -4017,11 +4202,10 @@ 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* mtv = dynamic_cast(&trackview); @@ -4034,19 +4218,36 @@ MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const n->channel(), patch_key.bank(), patch_key.program(), - n->note()); + note_value); } - mtv->set_note_highlight (n->note()); } 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 @@ -4085,40 +4286,30 @@ MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const } /** @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. + * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where + * bar is -1, 0 is audio samples and a positive integer is beat subdivisions. + * @return beat duration of p snapped to the grid subdivision underneath it. */ -framepos_t -MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const +Evoral::Beats +MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const { - PublicEditor& editor = trackview.editor (); - - const Evoral::Beats grid_beats = get_grid_beats(p); - - grid_frames = region_beats_to_region_frames (grid_beats); + TempoMap& map (trackview.session()->tempo_map()); + double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions); - /* Hack so that we always snap to the note that we are over, instead of snapping - to the next one if we're more than halfway through the one we're over. - */ - if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) { - p -= grid_frames / 2; - } - - return snap_frame_to_frame (p); -} - -/** Called when the selection has been cleared in any MidiRegionView. - * @param rv MidiRegionView that the selection was cleared in. - */ -void -MidiRegionView::selection_cleared (MidiRegionView* rv) -{ - if (rv == this) { - return; + if (divisions != 0 && shift_snap) { + const double qaf = map.quarter_note_at_frame (p + _region->position()); + /* 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. + */ + const Evoral::Beats grid_beats = get_grid_beats (p + _region->position()); + const double rem = eqaf - qaf; + if (rem >= 0.0) { + eqaf -= grid_beats.to_double(); + } } + const double session_start_off = _region->quarter_note() - midi_region()->start_beats(); - /* Clear our selection in sympathy; but don't signal the fact */ - clear_selection (false); + return Evoral::Beats (eqaf - session_start_off); } ChannelMode @@ -4141,9 +4332,30 @@ MidiRegionView::get_grid_beats(framepos_t pos) const { PublicEditor& editor = trackview.editor(); bool success = false; - Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos); + Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos); if (!success) { beats = Evoral::Beats(1); } return beats; } +uint8_t +MidiRegionView::y_to_note (double y) const +{ + int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1)) + + _current_range_min; + + if (n < 0) { + return 0; + } else if (n > 127) { + return 127; + } + + /* min due to rounding and/or off-by-one errors */ + return min ((uint8_t) n, _current_range_max); +} + +double +MidiRegionView::note_to_y(uint8_t note) const +{ + return contents_height() - (note + 1 - _current_range_min) * note_height() + 1; +}