2 Copyright (C) 2001-2007 Paul Davis
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/Control.hpp"
44 #include "evoral/midi_util.h"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas-program-change.h"
51 #include "ghostregion.h"
52 #include "gui_thread.h"
54 #include "midi_cut_buffer.h"
55 #include "midi_list_editor.h"
56 #include "midi_region_view.h"
57 #include "midi_streamview.h"
58 #include "midi_time_axis.h"
59 #include "midi_time_axis.h"
60 #include "midi_util.h"
61 #include "public_editor.h"
62 #include "selection.h"
63 #include "simpleline.h"
64 #include "streamview.h"
69 using namespace ARDOUR;
71 using namespace Editing;
72 using namespace ArdourCanvas;
73 using Gtkmm2ext::Keyboard;
75 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
76 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
77 : RegionView (parent, tv, r, spu, basic_color)
79 , _last_channel_selection(0xFFFF)
80 , _current_range_min(0)
81 , _current_range_max(0)
82 , _model_name(string())
83 , _custom_device_mode(string())
85 , _note_group(new ArdourCanvas::Group(*parent))
92 , _optimization_iterator (_events.end())
94 , no_sound_notes (false)
96 _note_group->raise_to_top();
97 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
100 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
101 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
102 TimeAxisViewItem::Visibility visibility)
103 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
105 , _last_channel_selection(0xFFFF)
106 , _model_name(string())
107 , _custom_device_mode(string())
109 , _note_group(new ArdourCanvas::Group(*parent))
115 , _sort_needed (true)
116 , _optimization_iterator (_events.end())
118 , no_sound_notes (false)
120 _note_group->raise_to_top();
121 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
124 MidiRegionView::MidiRegionView (const MidiRegionView& other)
125 : sigc::trackable(other)
128 , _last_channel_selection(0xFFFF)
129 , _model_name(string())
130 , _custom_device_mode(string())
132 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
138 , _sort_needed (true)
139 , _optimization_iterator (_events.end())
141 , no_sound_notes (false)
146 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
147 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
152 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
153 : RegionView (other, boost::shared_ptr<Region> (region))
155 , _last_channel_selection(0xFFFF)
156 , _model_name(string())
157 , _custom_device_mode(string())
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
165 , _sort_needed (true)
166 , _optimization_iterator (_events.end())
168 , no_sound_notes (false)
173 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
174 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
180 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
182 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
184 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
185 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
189 midi_region()->midi_source(0)->load_model();
192 _model = midi_region()->midi_source(0)->model();
193 _enable_display = false;
195 RegionView::init (basic_color, false);
197 compute_colors (basic_color);
199 set_height (trackview.current_height());
202 region_sync_changed ();
203 region_resized (ARDOUR::bounds_change);
206 reset_width_dependent_items (_pixel_width);
210 _enable_display = true;
213 display_model (_model);
217 group->raise_to_top();
218 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
220 midi_view()->signal_channel_mode_changed().connect(
221 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
223 midi_view()->signal_midi_patch_settings_changed().connect(
224 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
226 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
230 MidiRegionView::canvas_event(GdkEvent* ev)
232 if (!trackview.editor().internal_editing()) {
236 /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
237 to its items, which means that ev->type == GDK_SCROLL will never be seen
242 return scroll (&ev->scroll);
245 return key_press (&ev->key);
247 case GDK_KEY_RELEASE:
248 return key_release (&ev->key);
250 case GDK_BUTTON_PRESS:
251 return button_press (&ev->button);
253 case GDK_2BUTTON_PRESS:
256 case GDK_BUTTON_RELEASE:
257 return button_release (&ev->button);
259 case GDK_ENTER_NOTIFY:
260 return enter_notify (&ev->crossing);
262 case GDK_LEAVE_NOTIFY:
263 return leave_notify (&ev->crossing);
265 case GDK_MOTION_NOTIFY:
266 return motion (&ev->motion);
276 MidiRegionView::enter_notify (GdkEventCrossing* ev)
278 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
280 Keyboard::magic_widget_grab_focus();
283 if (trackview.editor().current_mouse_mode() == MouseRange) {
284 create_ghost_note (ev->x, ev->y);
291 MidiRegionView::leave_notify (GdkEventCrossing* ev)
293 trackview.editor().hide_verbose_canvas_cursor ();
300 MidiRegionView::button_press (GdkEventButton* ev)
304 group->w2i (_last_x, _last_y);
306 if (_mouse_state != SelectTouchDragging && ev->button == 1) {
307 _pressed_button = ev->button;
308 _mouse_state = Pressed;
312 _pressed_button = ev->button;
318 MidiRegionView::button_release (GdkEventButton* ev)
320 double event_x, event_y;
321 nframes64_t event_frame = 0;
325 group->w2i(event_x, event_y);
326 group->ungrab(ev->time);
327 event_frame = trackview.editor().pixel_to_frame(event_x);
329 if (ev->button == 3) {
331 } else if (_pressed_button != 1) {
335 switch (_mouse_state) {
336 case Pressed: // Clicked
337 switch (trackview.editor().current_mouse_mode()) {
341 maybe_select_by_position (ev, event_x, event_y);
347 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
351 create_note_at (event_x, event_y, beats, true);
359 case SelectRectDragging: // Select drag done
365 case AddDragging: // Add drag done
367 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
368 const double x = _drag_rect->property_x1();
369 const double length = trackview.editor().pixel_to_frame
370 (_drag_rect->property_x2() - _drag_rect->property_x1());
372 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), false);
378 create_ghost_note (ev->x, ev->y);
388 MidiRegionView::motion (GdkEventMotion* ev)
390 double event_x, event_y;
391 nframes64_t event_frame = 0;
395 group->w2i(event_x, event_y);
397 // convert event_x to global frame
398 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
399 trackview.editor().snap_to(event_frame);
400 // convert event_frame back to local coordinates relative to position
401 event_frame -= _region->position();
404 update_ghost_note (ev->x, ev->y);
407 /* any motion immediately hides velocity text that may have been visible */
409 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
410 (*i)->hide_velocity ();
413 switch (_mouse_state) {
414 case Pressed: // Maybe start a drag, if we've moved a bit
416 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
417 /* no appreciable movement since the button was pressed */
422 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
423 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
424 Gdk::Cursor(Gdk::FLEUR), ev->time);
427 _drag_start_x = event_x;
428 _drag_start_y = event_y;
430 _drag_rect = new ArdourCanvas::SimpleRect(*group);
431 _drag_rect->property_x1() = event_x;
432 _drag_rect->property_y1() = event_y;
433 _drag_rect->property_x2() = event_x;
434 _drag_rect->property_y2() = event_y;
435 _drag_rect->property_outline_what() = 0xFF;
436 _drag_rect->property_outline_color_rgba()
437 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
438 _drag_rect->property_fill_color_rgba()
439 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
441 _mouse_state = SelectRectDragging;
444 // Add note drag start
445 } else if (trackview.editor().internal_editing()) {
450 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
451 Gdk::Cursor(Gdk::FLEUR), ev->time);
454 _drag_start_x = event_x;
455 _drag_start_y = event_y;
457 _drag_rect = new ArdourCanvas::SimpleRect(*group);
458 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
460 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
461 midi_stream_view()->y_to_note(event_y));
462 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
463 _drag_rect->property_y2() = _drag_rect->property_y1()
464 + floor(midi_stream_view()->note_height());
465 _drag_rect->property_outline_what() = 0xFF;
466 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
467 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
469 _mouse_state = AddDragging;
475 case SelectRectDragging: // Select drag motion
476 case AddDragging: // Add note drag motion
480 GdkModifierType state;
481 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
486 if (_mouse_state == AddDragging)
487 event_x = trackview.editor().frame_to_pixel(event_frame);
490 if (event_x > _drag_start_x)
491 _drag_rect->property_x2() = event_x;
493 _drag_rect->property_x1() = event_x;
496 if (_drag_rect && _mouse_state == SelectRectDragging) {
497 if (event_y > _drag_start_y)
498 _drag_rect->property_y2() = event_y;
500 _drag_rect->property_y1() = event_y;
502 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
508 case SelectTouchDragging:
520 MidiRegionView::scroll (GdkEventScroll* ev)
522 if (_selection.empty()) {
526 trackview.editor().hide_verbose_canvas_cursor ();
528 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
530 if (ev->direction == GDK_SCROLL_UP) {
531 change_velocities (true, fine, false);
532 } else if (ev->direction == GDK_SCROLL_DOWN) {
533 change_velocities (false, fine, false);
539 MidiRegionView::key_press (GdkEventKey* ev)
541 /* since GTK bindings are generally activated on press, and since
542 detectable auto-repeat is the name of the game and only sends
543 repeated presses, carry out key actions at key press, not release.
546 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
547 _mouse_state = SelectTouchDragging;
550 } else if (ev->keyval == GDK_Escape) {
554 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
556 bool start = (ev->keyval == GDK_comma);
557 bool end = (ev->keyval == GDK_period);
558 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
559 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
561 change_note_lengths (fine, shorter, start, end);
565 } else if (ev->keyval == GDK_Delete) {
570 } else if (ev->keyval == GDK_Tab) {
572 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
573 goto_previous_note ();
579 } else if (ev->keyval == GDK_Up) {
581 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
582 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
584 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
585 change_velocities (true, fine, allow_smush);
587 transpose (true, fine, allow_smush);
591 } else if (ev->keyval == GDK_Down) {
593 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
594 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
596 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
597 change_velocities (false, fine, allow_smush);
599 transpose (false, fine, allow_smush);
603 } else if (ev->keyval == GDK_Left) {
608 } else if (ev->keyval == GDK_Right) {
613 } else if (ev->keyval == GDK_Control_L) {
616 } else if (ev->keyval == GDK_r) {
617 /* yes, this steals r */
618 if (midi_view()->midi_track()->step_editing()) {
619 midi_view()->step_edit_rest ();
628 MidiRegionView::key_release (GdkEventKey* ev)
630 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
638 MidiRegionView::show_list_editor ()
641 _list_editor = new MidiListEditor (trackview.session(), midi_region());
643 _list_editor->present ();
646 /** Add a note to the model, and the view, at a canvas (click) coordinate.
647 * \param x horizontal position in pixels
648 * \param y vertical position in pixels
649 * \param length duration of the note in beats, which will be snapped to the grid
650 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
653 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
655 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
656 MidiStreamView* const view = mtv->midi_view();
658 double note = midi_stream_view()->y_to_note(y);
661 assert(note <= 127.0);
663 // Start of note in frames relative to region start
664 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
665 assert(start_frames >= 0);
668 length = frames_to_beats(
669 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
671 assert (length != 0);
674 length = frames_to_beats (beats_to_frames (length) - 1);
677 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
678 frames_to_beats(start_frames + _region->start()), length,
679 (uint8_t)note, 0x40));
681 if (_model->contains (new_note)) {
685 view->update_note_range(new_note->note());
687 MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
689 _model->apply_command(*trackview.session(), cmd);
691 play_midi_note (new_note);
695 MidiRegionView::clear_events()
700 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
701 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
706 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
711 _pgm_changes.clear();
713 _optimization_iterator = _events.end();
718 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
721 content_connection.disconnect ();
722 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
726 if (_enable_display) {
732 MidiRegionView::start_diff_command(string name)
734 if (!_diff_command) {
735 _diff_command = _model->new_diff_command(name);
740 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
743 _diff_command->add(note);
746 _marked_for_selection.insert(note);
749 _marked_for_velocity.insert(note);
754 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
756 if (_diff_command && ev->note()) {
757 _diff_command->remove(ev->note());
762 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
763 MidiModel::DiffCommand::Property property,
767 _diff_command->change (ev->note(), property, val);
772 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
773 MidiModel::DiffCommand::Property property,
774 Evoral::MusicalTime val)
777 _diff_command->change (ev->note(), property, val);
782 MidiRegionView::apply_diff ()
786 if (!_diff_command) {
790 if ((add_or_remove = _diff_command->adds_or_removes())) {
791 // Mark all selected notes for selection when model reloads
792 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
793 _marked_for_selection.insert((*i)->note());
797 _model->apply_command(*trackview.session(), _diff_command);
799 midi_view()->midi_track()->playlist_modified();
803 _marked_for_selection.clear();
806 _marked_for_velocity.clear();
810 MidiRegionView::apply_diff_as_subcommand()
814 if (!_diff_command) {
818 if ((add_or_remove = _diff_command->adds_or_removes())) {
819 // Mark all selected notes for selection when model reloads
820 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
821 _marked_for_selection.insert((*i)->note());
825 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
827 midi_view()->midi_track()->playlist_modified();
830 _marked_for_selection.clear();
832 _marked_for_velocity.clear();
837 MidiRegionView::abort_command()
839 delete _diff_command;
845 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
847 if (_optimization_iterator != _events.end()) {
848 ++_optimization_iterator;
851 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
852 return *_optimization_iterator;
855 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
856 if ((*_optimization_iterator)->note() == note) {
857 return *_optimization_iterator;
865 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
867 MidiModel::Notes notes;
868 _model->get_notes (notes, op, val, chan_mask);
870 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
871 CanvasNoteEvent* cne = find_canvas_note (*n);
879 MidiRegionView::redisplay_model()
881 // Don't redisplay the model if we're currently recording and displaying that
887 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
891 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
895 MidiModel::ReadLock lock(_model->read_lock());
897 MidiModel::Notes& notes (_model->notes());
898 _optimization_iterator = _events.begin();
900 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
902 boost::shared_ptr<NoteType> note (*n);
903 CanvasNoteEvent* cne;
906 if (note_in_region_range (note, visible)) {
908 if ((cne = find_canvas_note (note)) != 0) {
915 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
917 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
929 add_note (note, visible);
934 if ((cne = find_canvas_note (note)) != 0) {
942 /* remove note items that are no longer valid */
944 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
945 if (!(*i)->valid ()) {
947 i = _events.erase (i);
954 display_program_changes();
956 _marked_for_selection.clear ();
957 _marked_for_velocity.clear ();
959 /* we may have caused _events to contain things out of order (e.g. if a note
960 moved earlier or later). we don't generally need them in time order, but
961 make a note that a sort is required for those cases that require it.
968 MidiRegionView::display_program_changes()
970 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
975 Glib::Mutex::Lock lock (control->list()->lock());
977 uint8_t channel = control->parameter().channel();
979 for (AutomationList::const_iterator event = control->list()->begin();
980 event != control->list()->end(); ++event) {
981 double event_time = (*event)->when;
982 double program_number = floor((*event)->value + 0.5);
984 // Get current value of bank select MSB at time of the program change
985 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
986 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
988 if (msb_control != 0) {
989 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
992 // Get current value of bank select LSB at time of the program change
993 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
994 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
996 if (lsb_control != 0) {
997 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
1000 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1002 boost::shared_ptr<MIDI::Name::Patch> patch =
1003 MIDI::Name::MidiPatchManager::instance().find_patch(
1004 _model_name, _custom_device_mode, channel, patch_key);
1006 PCEvent program_change(event_time, uint8_t(program_number), channel);
1009 add_pgm_change(program_change, patch->name());
1012 snprintf(buf, 4, "%d", int(program_number));
1013 add_pgm_change(program_change, buf);
1019 MidiRegionView::display_sysexes()
1021 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1022 Evoral::MusicalTime time = (*i)->time();
1027 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1028 str << int((*i)->buffer()[b]);
1029 if (b != (*i)->size() -1) {
1033 string text = str.str();
1035 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1037 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1039 double height = midi_stream_view()->contents_height();
1041 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1042 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1044 // Show unless program change is beyond the region bounds
1045 if (time - _region->start() >= _region->length() || time < _region->start()) {
1051 _sys_exes.push_back(sysex);
1056 MidiRegionView::~MidiRegionView ()
1058 in_destructor = true;
1060 note_delete_connection.disconnect ();
1062 delete _list_editor;
1064 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1066 if (_active_notes) {
1073 delete _diff_command;
1077 MidiRegionView::region_resized (const PropertyChange& what_changed)
1079 RegionView::region_resized(what_changed);
1081 if (what_changed.contains (ARDOUR::Properties::position)) {
1082 set_duration(_region->length(), 0);
1083 if (_enable_display) {
1090 MidiRegionView::reset_width_dependent_items (double pixel_width)
1092 RegionView::reset_width_dependent_items(pixel_width);
1093 assert(_pixel_width == pixel_width);
1095 if (_enable_display) {
1101 MidiRegionView::set_height (double height)
1103 static const double FUDGE = 2.0;
1104 const double old_height = _height;
1105 RegionView::set_height(height);
1106 _height = height - FUDGE;
1108 apply_note_range(midi_stream_view()->lowest_note(),
1109 midi_stream_view()->highest_note(),
1110 height != old_height + FUDGE);
1113 name_pixbuf->raise_to_top();
1118 /** Apply the current note range from the stream view
1119 * by repositioning/hiding notes as necessary
1122 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1124 if (!_enable_display) {
1128 if (!force && _current_range_min == min && _current_range_max == max) {
1132 _current_range_min = min;
1133 _current_range_max = max;
1135 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1136 CanvasNoteEvent* event = *i;
1137 boost::shared_ptr<NoteType> note (event->note());
1139 if (note->note() < _current_range_min ||
1140 note->note() > _current_range_max) {
1146 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1148 const double y1 = midi_stream_view()->note_to_y(note->note());
1149 const double y2 = y1 + floor(midi_stream_view()->note_height());
1151 cnote->property_y1() = y1;
1152 cnote->property_y2() = y2;
1154 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1156 double x = trackview.editor().frame_to_pixel(
1157 beats_to_frames(note->time()) - _region->start());
1158 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1159 double y = midi_stream_view()->note_to_y(event->note()->note())
1160 + ((diamond_size-2.0) / 4.0);
1162 chit->set_height (diamond_size);
1163 chit->move (x - chit->x1(), y - chit->y1());
1170 MidiRegionView::add_ghost (TimeAxisView& tv)
1174 double unit_position = _region->position () / samples_per_unit;
1175 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1176 MidiGhostRegion* ghost;
1178 if (mtv && mtv->midi_view()) {
1179 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1180 to allow having midi notes on top of note lines and waveforms.
1182 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1184 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1187 ghost->set_height ();
1188 ghost->set_duration (_region->length() / samples_per_unit);
1189 ghosts.push_back (ghost);
1191 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1192 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1193 ghost->add_note(note);
1197 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1203 /** Begin tracking note state for successive calls to add_event
1206 MidiRegionView::begin_write()
1208 assert(!_active_notes);
1209 _active_notes = new CanvasNote*[128];
1210 for (unsigned i=0; i < 128; ++i) {
1211 _active_notes[i] = 0;
1216 /** Destroy note state for add_event
1219 MidiRegionView::end_write()
1221 delete[] _active_notes;
1223 _marked_for_selection.clear();
1224 _marked_for_velocity.clear();
1228 /** Resolve an active MIDI note (while recording).
1231 MidiRegionView::resolve_note(uint8_t note, double end_time)
1233 if (midi_view()->note_mode() != Sustained) {
1237 if (_active_notes && _active_notes[note]) {
1238 const nframes64_t end_time_frames = beats_to_frames(end_time);
1239 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1240 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1241 _active_notes[note] = 0;
1246 /** Extend active notes to rightmost edge of region (if length is changed)
1249 MidiRegionView::extend_active_notes()
1251 if (!_active_notes) {
1255 for (unsigned i=0; i < 128; ++i) {
1256 if (_active_notes[i]) {
1257 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1263 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1265 if (no_sound_notes || !trackview.editor().sound_notes()) {
1269 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1272 route_ui->midi_track()->write_immediate_event(
1273 note->on_event().size(), note->on_event().buffer());
1275 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1276 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1277 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1278 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1279 note_length_ms, G_PRIORITY_DEFAULT);
1283 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1285 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1288 route_ui->midi_track()->write_immediate_event(
1289 note->off_event().size(), note->off_event().buffer());
1295 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1297 const nframes64_t note_start_frames = beats_to_frames(note->time());
1299 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1300 (note_start_frames < _region->start());
1302 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1303 (note->note() <= midi_stream_view()->highest_note());
1309 MidiRegionView::update_note (CanvasNote* ev)
1311 boost::shared_ptr<NoteType> note = ev->note();
1313 const nframes64_t note_start_frames = beats_to_frames(note->time());
1314 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1316 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1317 const double y1 = midi_stream_view()->note_to_y(note->note());
1318 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1320 ev->property_x1() = x;
1321 ev->property_y1() = y1;
1322 if (note->length() > 0) {
1323 ev->property_x2() = note_endpixel;
1325 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1327 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1329 if (note->length() == 0) {
1330 if (_active_notes) {
1331 assert(note->note() < 128);
1332 // If this note is already active there's a stuck note,
1333 // finish the old note rectangle
1334 if (_active_notes[note->note()]) {
1335 CanvasNote* const old_rect = _active_notes[note->note()];
1336 boost::shared_ptr<NoteType> old_note = old_rect->note();
1337 old_rect->property_x2() = x;
1338 old_rect->property_outline_what() = (guint32) 0xF;
1340 _active_notes[note->note()] = ev;
1342 /* outline all but right edge */
1343 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1345 /* outline all edges */
1346 ev->property_outline_what() = (guint32) 0xF;
1351 MidiRegionView::update_hit (CanvasHit* ev)
1353 boost::shared_ptr<NoteType> note = ev->note();
1355 const nframes64_t note_start_frames = beats_to_frames(note->time());
1356 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1357 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1358 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1363 /** Add a MIDI note to the view (with length).
1365 * If in sustained mode, notes with length 0 will be considered active
1366 * notes, and resolve_note should be called when the corresponding note off
1367 * event arrives, to properly display the note.
1370 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1372 CanvasNoteEvent* event = 0;
1374 assert(note->time() >= 0);
1375 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1377 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1379 if (midi_view()->note_mode() == Sustained) {
1381 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1383 update_note (ev_rect);
1387 MidiGhostRegion* gr;
1389 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1390 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1391 gr->add_note(ev_rect);
1395 } else if (midi_view()->note_mode() == Percussive) {
1397 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1399 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1401 update_hit (ev_diamond);
1410 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1411 note_selected(event, true);
1414 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1415 event->show_velocity();
1417 event->on_channel_selection_change(_last_channel_selection);
1418 _events.push_back(event);
1429 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1430 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1432 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1434 start_diff_command (_("step add"));
1435 diff_add_note (new_note, true, false);
1438 /* potentially extend region to hold new note */
1440 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1441 nframes64_t region_end = _region->position() + _region->length() - 1;
1443 if (end_frame > region_end) {
1444 _region->set_length (end_frame, this);
1451 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1453 assert(program.time >= 0);
1455 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1456 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1458 double height = midi_stream_view()->contents_height();
1460 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1461 new CanvasProgramChange(*this, *group,
1466 _custom_device_mode,
1467 program.time, program.channel, program.value));
1469 // Show unless program change is beyond the region bounds
1470 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1476 _pgm_changes.push_back(pgm_change);
1480 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1482 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1483 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1484 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1486 if (msb_control != 0) {
1487 msb = int(msb_control->get_float(true, time));
1488 cerr << "got msb " << msb;
1491 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1492 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1494 if (lsb_control != 0) {
1495 lsb = lsb_control->get_float(true, time);
1496 cerr << " got lsb " << lsb;
1499 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1500 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1501 float program_number = -1.0;
1502 if (program_control != 0) {
1503 program_number = program_control->get_float(true, time);
1504 cerr << " got program " << program_number << endl;
1507 key.msb = (int) floor(msb + 0.5);
1508 key.lsb = (int) floor(lsb + 0.5);
1509 key.program_number = (int) floor(program_number + 0.5);
1510 assert(key.is_sane());
1515 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1517 // TODO: Get the real event here and alter them at the original times
1518 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1519 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1520 if (msb_control != 0) {
1521 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1524 // TODO: Get the real event here and alter them at the original times
1525 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1526 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1527 if (lsb_control != 0) {
1528 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1531 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1532 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1534 assert(program_control != 0);
1535 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1541 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1543 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1544 alter_program_change(program_change_event, new_patch);
1548 MidiRegionView::previous_program(CanvasProgramChange& program)
1550 MIDI::Name::PatchPrimaryKey key;
1551 get_patch_key_at(program.event_time(), program.channel(), key);
1553 boost::shared_ptr<MIDI::Name::Patch> patch =
1554 MIDI::Name::MidiPatchManager::instance().previous_patch(
1556 _custom_device_mode,
1560 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1562 alter_program_change(program_change_event, patch->patch_primary_key());
1567 MidiRegionView::next_program(CanvasProgramChange& program)
1569 MIDI::Name::PatchPrimaryKey key;
1570 get_patch_key_at(program.event_time(), program.channel(), key);
1572 boost::shared_ptr<MIDI::Name::Patch> patch =
1573 MIDI::Name::MidiPatchManager::instance().next_patch(
1575 _custom_device_mode,
1579 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1581 alter_program_change(program_change_event, patch->patch_primary_key());
1586 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1588 if (_selection.empty()) {
1592 if (_selection.erase (cne) > 0) {
1593 cerr << "Erased a CNE from selection\n";
1598 MidiRegionView::delete_selection()
1600 if (_selection.empty()) {
1604 start_diff_command (_("delete selection"));
1606 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1607 if ((*i)->selected()) {
1608 _diff_command->remove((*i)->note());
1618 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1620 start_diff_command (_("delete note"));
1621 _diff_command->remove (n);
1624 trackview.editor().hide_verbose_canvas_cursor ();
1628 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1630 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1631 if ((*i)->selected() && (*i) != ev) {
1632 (*i)->set_selected(false);
1633 (*i)->hide_velocity();
1641 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1643 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1646 Selection::iterator tmp = i;
1649 (*i)->set_selected (false);
1650 _selection.erase (i);
1659 /* don't bother with removing this regionview from the editor selection,
1660 since we're about to add another note, and thus put/keep this
1661 regionview in the editor selection.
1664 if (!ev->selected()) {
1665 add_to_selection (ev);
1670 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1672 uint8_t low_note = 127;
1673 uint8_t high_note = 0;
1674 MidiModel::Notes& notes (_model->notes());
1675 _optimization_iterator = _events.begin();
1681 if (extend && _selection.empty()) {
1687 /* scan existing selection to get note range */
1689 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1690 if ((*i)->note()->note() < low_note) {
1691 low_note = (*i)->note()->note();
1693 if ((*i)->note()->note() > high_note) {
1694 high_note = (*i)->note()->note();
1698 low_note = min (low_note, notenum);
1699 high_note = max (high_note, notenum);
1702 no_sound_notes = true;
1704 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1706 boost::shared_ptr<NoteType> note (*n);
1707 CanvasNoteEvent* cne;
1708 bool select = false;
1710 if (((1 << note->channel()) & channel_mask) != 0) {
1712 if ((note->note() >= low_note && note->note() <= high_note)) {
1715 } else if (note->note() == notenum) {
1721 if ((cne = find_canvas_note (note)) != 0) {
1722 // extend is false because we've taken care of it,
1723 // since it extends by time range, not pitch.
1724 note_selected (cne, add, false);
1728 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1732 no_sound_notes = false;
1736 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1738 MidiModel::Notes& notes (_model->notes());
1739 _optimization_iterator = _events.begin();
1741 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1743 boost::shared_ptr<NoteType> note (*n);
1744 CanvasNoteEvent* cne;
1746 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1747 if ((cne = find_canvas_note (note)) != 0) {
1748 if (cne->selected()) {
1749 note_deselected (cne);
1751 note_selected (cne, true, false);
1759 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1762 clear_selection_except(ev);
1767 if (!ev->selected()) {
1768 add_to_selection (ev);
1772 /* find end of latest note selected, select all between that and the start of "ev" */
1774 Evoral::MusicalTime earliest = DBL_MAX;
1775 Evoral::MusicalTime latest = 0;
1777 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1778 if ((*i)->note()->end_time() > latest) {
1779 latest = (*i)->note()->end_time();
1781 if ((*i)->note()->time() < earliest) {
1782 earliest = (*i)->note()->time();
1786 if (ev->note()->end_time() > latest) {
1787 latest = ev->note()->end_time();
1790 if (ev->note()->time() < earliest) {
1791 earliest = ev->note()->time();
1794 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1796 /* find notes entirely within OR spanning the earliest..latest range */
1798 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1799 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1800 add_to_selection (*i);
1804 /* if events were guaranteed to be time sorted, we could do this.
1805 but as of sept 10th 2009, they no longer are.
1808 if ((*i)->note()->time() > latest) {
1817 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1819 remove_from_selection (ev);
1823 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1833 // TODO: Make this faster by storing the last updated selection rect, and only
1834 // adjusting things that are in the area that appears/disappeared.
1835 // We probably need a tree to be able to find events in O(log(n)) time.
1837 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1839 /* check if any corner of the note is inside the rect
1842 1) this is computing "touched by", not "contained by" the rect.
1843 2) this does not require that events be sorted in time.
1846 const double ix1 = (*i)->x1();
1847 const double ix2 = (*i)->x2();
1848 const double iy1 = (*i)->y1();
1849 const double iy2 = (*i)->y2();
1851 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1852 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1853 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1854 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1857 if (!(*i)->selected()) {
1858 add_to_selection (*i);
1860 } else if ((*i)->selected()) {
1861 // Not inside rectangle
1862 remove_from_selection (*i);
1868 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1870 Selection::iterator i = _selection.find (ev);
1872 if (i != _selection.end()) {
1873 _selection.erase (i);
1876 ev->set_selected (false);
1877 ev->hide_velocity ();
1879 if (_selection.empty()) {
1880 PublicEditor& editor (trackview.editor());
1881 editor.get_selection().remove (this);
1886 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1888 bool add_mrv_selection = false;
1890 if (_selection.empty()) {
1891 add_mrv_selection = true;
1894 if (_selection.insert (ev).second) {
1895 ev->set_selected (true);
1896 play_midi_note ((ev)->note());
1899 if (add_mrv_selection) {
1900 PublicEditor& editor (trackview.editor());
1901 editor.get_selection().add (this);
1906 MidiRegionView::move_selection(double dx, double dy)
1908 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1909 (*i)->move_event(dx, dy);
1914 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1916 assert (!_selection.empty());
1918 uint8_t lowest_note_in_selection = 127;
1919 uint8_t highest_note_in_selection = 0;
1920 uint8_t highest_note_difference = 0;
1922 // find highest and lowest notes first
1924 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1925 uint8_t pitch = (*i)->note()->note();
1926 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1927 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1931 cerr << "dnote: " << (int) dnote << endl;
1932 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1933 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1934 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1935 << int(highest_note_in_selection) << endl;
1936 cerr << "selection size: " << _selection.size() << endl;
1937 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1940 // Make sure the note pitch does not exceed the MIDI standard range
1941 if (highest_note_in_selection + dnote > 127) {
1942 highest_note_difference = highest_note_in_selection - 127;
1945 start_diff_command(_("move notes"));
1947 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1949 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1952 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1954 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1957 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1963 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1965 uint8_t original_pitch = (*i)->note()->note();
1966 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1968 // keep notes in standard midi range
1969 clamp_to_0_127(new_pitch);
1971 // keep original pitch if note is dragged outside valid midi range
1972 if ((original_pitch != 0 && new_pitch == 0)
1973 || (original_pitch != 127 && new_pitch == 127)) {
1974 new_pitch = original_pitch;
1977 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1978 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1980 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1985 // care about notes being moved beyond the upper/lower bounds on the canvas
1986 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1987 highest_note_in_selection > midi_stream_view()->highest_note()) {
1988 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1993 MidiRegionView::snap_pixel_to_frame(double x)
1995 PublicEditor& editor = trackview.editor();
1996 // x is region relative, convert it to global absolute frames
1997 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1998 editor.snap_to(frame);
1999 return frame - _region->position(); // convert back to region relative
2003 MidiRegionView::snap_frame_to_frame(nframes64_t x)
2005 PublicEditor& editor = trackview.editor();
2006 // x is region relative, convert it to global absolute frames
2007 nframes64_t frame = x + _region->position();
2008 editor.snap_to(frame);
2009 return frame - _region->position(); // convert back to region relative
2013 MidiRegionView::snap_to_pixel(double x)
2015 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2019 MidiRegionView::get_position_pixels()
2021 nframes64_t region_frame = get_position();
2022 return trackview.editor().frame_to_pixel(region_frame);
2026 MidiRegionView::get_end_position_pixels()
2028 nframes64_t frame = get_position() + get_duration ();
2029 return trackview.editor().frame_to_pixel(frame);
2033 MidiRegionView::beats_to_frames(double beats) const
2035 return _time_converter.to(beats);
2039 MidiRegionView::frames_to_beats(nframes64_t frames) const
2041 return _time_converter.from(frames);
2045 MidiRegionView::begin_resizing (bool /*at_front*/)
2047 _resize_data.clear();
2049 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2050 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2052 // only insert CanvasNotes into the map
2054 NoteResizeData *resize_data = new NoteResizeData();
2055 resize_data->canvas_note = note;
2057 // create a new SimpleRect from the note which will be the resize preview
2058 SimpleRect *resize_rect = new SimpleRect(
2059 *group, note->x1(), note->y1(), note->x2(), note->y2());
2061 // calculate the colors: get the color settings
2062 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2063 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2066 // make the resize preview notes more transparent and bright
2067 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2069 // calculate color based on note velocity
2070 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2071 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2075 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2076 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2078 resize_data->resize_rect = resize_rect;
2079 _resize_data.push_back(resize_data);
2084 /** Update resizing notes while user drags.
2085 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2086 * @param at_front which end of the note (true == note on, false == note off)
2087 * @param delta_x change in mouse position since the start of the drag
2088 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2089 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2090 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2091 * as the \a primary note.
2094 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2096 bool cursor_set = false;
2098 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2099 SimpleRect* resize_rect = (*i)->resize_rect;
2100 CanvasNote* canvas_note = (*i)->canvas_note;
2105 current_x = canvas_note->x1() + delta_x;
2107 current_x = primary->x1() + delta_x;
2111 current_x = canvas_note->x2() + delta_x;
2113 current_x = primary->x2() + delta_x;
2118 resize_rect->property_x1() = snap_to_pixel(current_x);
2119 resize_rect->property_x2() = canvas_note->x2();
2121 resize_rect->property_x2() = snap_to_pixel(current_x);
2122 resize_rect->property_x1() = canvas_note->x1();
2128 beats = snap_pixel_to_frame (current_x);
2129 beats = frames_to_beats (beats);
2134 if (beats < canvas_note->note()->end_time()) {
2135 len = canvas_note->note()->time() - beats;
2136 len += canvas_note->note()->length();
2141 if (beats >= canvas_note->note()->end_time()) {
2142 len = beats - canvas_note->note()->time();
2149 snprintf (buf, sizeof (buf), "%.3g beats", len);
2150 trackview.editor().show_verbose_canvas_cursor_with (buf);
2159 /** Finish resizing notes when the user releases the mouse button.
2160 * Parameters the same as for \a update_resizing().
2163 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2165 start_diff_command(_("resize notes"));
2167 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2168 CanvasNote* canvas_note = (*i)->canvas_note;
2169 SimpleRect* resize_rect = (*i)->resize_rect;
2174 current_x = canvas_note->x1() + delta_x;
2176 current_x = primary->x1() + delta_x;
2180 current_x = canvas_note->x2() + delta_x;
2182 current_x = primary->x2() + delta_x;
2186 current_x = snap_pixel_to_frame (current_x);
2187 current_x = frames_to_beats (current_x);
2189 if (at_front && current_x < canvas_note->note()->end_time()) {
2190 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2192 double len = canvas_note->note()->time() - current_x;
2193 len += canvas_note->note()->length();
2196 /* XXX convert to beats */
2197 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2202 double len = current_x - canvas_note->note()->time();
2205 /* XXX convert to beats */
2206 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2214 _resize_data.clear();
2219 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2221 uint8_t new_velocity;
2224 new_velocity = event->note()->velocity() + velocity;
2225 clamp_to_0_127(new_velocity);
2227 new_velocity = velocity;
2230 event->set_selected (event->selected()); // change color
2232 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2236 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2241 new_note = event->note()->note() + note;
2246 clamp_to_0_127 (new_note);
2247 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2251 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2253 bool change_start = false;
2254 bool change_length = false;
2255 Evoral::MusicalTime new_start = 0;
2256 Evoral::MusicalTime new_length = 0;
2258 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2260 front_delta: if positive - move the start of the note later in time (shortening it)
2261 if negative - move the start of the note earlier in time (lengthening it)
2263 end_delta: if positive - move the end of the note later in time (lengthening it)
2264 if negative - move the end of the note earlier in time (shortening it)
2268 if (front_delta < 0) {
2270 if (event->note()->time() < -front_delta) {
2273 new_start = event->note()->time() + front_delta; // moves earlier
2276 /* start moved toward zero, so move the end point out to where it used to be.
2277 Note that front_delta is negative, so this increases the length.
2280 new_length = event->note()->length() - front_delta;
2281 change_start = true;
2282 change_length = true;
2286 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2288 if (new_pos < event->note()->end_time()) {
2289 new_start = event->note()->time() + front_delta;
2290 /* start moved toward the end, so move the end point back to where it used to be */
2291 new_length = event->note()->length() - front_delta;
2292 change_start = true;
2293 change_length = true;
2300 bool can_change = true;
2301 if (end_delta < 0) {
2302 if (event->note()->length() < -end_delta) {
2308 new_length = event->note()->length() + end_delta;
2309 change_length = true;
2314 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2317 if (change_length) {
2318 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2323 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2325 Evoral::MusicalTime new_time;
2329 if (event->note()->time() < -delta) {
2332 new_time = event->note()->time() + delta;
2335 new_time = event->note()->time() + delta;
2341 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2345 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2349 if (_selection.empty()) {
2364 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2365 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2371 start_diff_command(_("change velocities"));
2373 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2374 Selection::iterator next = i;
2376 change_note_velocity (*i, delta, true);
2380 if (!_selection.empty()) {
2382 snprintf (buf, sizeof (buf), "Vel %d",
2383 (int) (*_selection.begin())->note()->velocity());
2384 trackview.editor().show_verbose_canvas_cursor_with (buf);
2392 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2394 if (_selection.empty()) {
2411 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2413 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2417 if ((int8_t) (*i)->note()->note() + delta > 127) {
2424 start_diff_command (_("transpose"));
2426 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2427 Selection::iterator next = i;
2429 change_note_note (*i, delta, true);
2437 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2439 Evoral::MusicalTime delta;
2444 /* grab the current grid distance */
2446 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2448 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2449 cerr << "Grid type not available as beats - TO BE FIXED\n";
2458 start_diff_command (_("change note lengths"));
2460 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2461 Selection::iterator next = i;
2464 /* note the negation of the delta for start */
2466 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2475 MidiRegionView::nudge_notes (bool forward)
2477 if (_selection.empty()) {
2481 /* pick a note as the point along the timeline to get the nudge distance.
2482 its not necessarily the earliest note, so we may want to pull the notes out
2483 into a vector and sort before using the first one.
2486 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2488 nframes64_t distance;
2490 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2492 /* grid is off - use nudge distance */
2494 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2500 nframes64_t next_pos = ref_point;
2503 /* XXX need check on max_frames, but that needs max_frames64 or something */
2506 if (next_pos == 0) {
2512 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2513 distance = ref_point - next_pos;
2516 if (distance == 0) {
2520 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2526 start_diff_command (_("nudge"));
2528 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2529 Selection::iterator next = i;
2531 change_note_time (*i, delta, true);
2539 MidiRegionView::change_channel(uint8_t channel)
2541 start_diff_command(_("change channel"));
2542 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2543 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2551 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2553 if (_mouse_state == SelectTouchDragging) {
2554 note_selected (ev, true);
2557 show_verbose_canvas_cursor (ev->note ());
2561 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2563 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2564 (*i)->hide_velocity ();
2567 trackview.editor().hide_verbose_canvas_cursor ();
2571 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2573 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2575 display_model(msrc->model());
2579 MidiRegionView::set_frame_color()
2582 if (_selected && should_show_selection) {
2583 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2585 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2591 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2595 case FilterChannels:
2596 _force_channel = -1;
2599 _force_channel = mask;
2600 mask = 0xFFFF; // Show all notes as active (below)
2603 // Update notes for selection
2604 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2605 (*i)->on_channel_selection_change(mask);
2608 _last_channel_selection = mask;
2612 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2614 _model_name = model;
2615 _custom_device_mode = custom_device_mode;
2620 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2622 if (_selection.empty()) {
2626 PublicEditor& editor (trackview.editor());
2631 editor.get_cut_buffer().add (selection_as_cut_buffer());
2639 start_diff_command();
2641 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2647 diff_remove_note (*i);
2657 MidiRegionView::selection_as_cut_buffer () const
2661 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2662 NoteType* n = (*i)->note().get();
2663 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2666 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2673 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2679 start_diff_command (_("paste"));
2681 Evoral::MusicalTime beat_delta;
2682 Evoral::MusicalTime paste_pos_beats;
2683 Evoral::MusicalTime duration;
2684 Evoral::MusicalTime end_point = 0;
2686 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2687 paste_pos_beats = frames_to_beats (pos - _region->position());
2688 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2689 paste_pos_beats = 0;
2691 _selection.clear ();
2693 for (int n = 0; n < (int) times; ++n) {
2695 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2697 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2698 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2700 /* make all newly added notes selected */
2702 diff_add_note (copied_note, true);
2703 end_point = copied_note->end_time();
2706 paste_pos_beats += duration;
2709 /* if we pasted past the current end of the region, extend the region */
2711 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2712 nframes64_t region_end = _region->position() + _region->length() - 1;
2714 if (end_frame > region_end) {
2716 trackview.session()->begin_reversible_command (_("paste"));
2718 _region->clear_history ();
2719 _region->set_length (end_frame, this);
2720 trackview.session()->add_command (new StatefulDiffCommand (_region));
2726 struct EventNoteTimeEarlyFirstComparator {
2727 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2728 return a->note()->time() < b->note()->time();
2733 MidiRegionView::time_sort_events ()
2735 if (!_sort_needed) {
2739 EventNoteTimeEarlyFirstComparator cmp;
2742 _sort_needed = false;
2746 MidiRegionView::goto_next_note ()
2748 // nframes64_t pos = -1;
2749 bool use_next = false;
2751 if (_events.back()->selected()) {
2755 time_sort_events ();
2757 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2758 if ((*i)->selected()) {
2761 } else if (use_next) {
2763 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2768 /* use the first one */
2770 unique_select (_events.front());
2775 MidiRegionView::goto_previous_note ()
2777 // nframes64_t pos = -1;
2778 bool use_next = false;
2780 if (_events.front()->selected()) {
2784 time_sort_events ();
2786 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2787 if ((*i)->selected()) {
2790 } else if (use_next) {
2792 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2797 /* use the last one */
2799 unique_select (*(_events.rbegin()));
2803 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2805 bool had_selected = false;
2807 time_sort_events ();
2809 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2810 if ((*i)->selected()) {
2811 selected.insert ((*i)->note());
2812 had_selected = true;
2816 if (allow_all_if_none_selected && !had_selected) {
2817 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2818 selected.insert ((*i)->note());
2824 MidiRegionView::update_ghost_note (double x, double y)
2830 nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2831 trackview.editor().snap_to (f);
2832 f -= _region->position ();
2835 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2840 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2842 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2843 _ghost_note->note()->set_length (length);
2844 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2846 update_note (_ghost_note);
2848 show_verbose_canvas_cursor (_ghost_note->note ());
2852 MidiRegionView::create_ghost_note (double x, double y)
2857 boost::shared_ptr<NoteType> g (new NoteType);
2858 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2859 update_ghost_note (x, y);
2860 _ghost_note->show ();
2865 show_verbose_canvas_cursor (_ghost_note->note ());
2869 MidiRegionView::snap_changed ()
2875 create_ghost_note (_last_ghost_x, _last_ghost_y);
2879 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2882 snprintf (buf, sizeof (buf), "%s (%d)\nVel %d",
2883 Evoral::midi_note_name (n->note()).c_str(),
2885 (int) n->velocity());
2886 trackview.editor().show_verbose_canvas_cursor_with (buf);
2890 MidiRegionView::drop_down_keys ()
2892 _mouse_state = None;
2896 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
2898 double note = midi_stream_view()->y_to_note(y);
2900 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2902 cerr << "Selecting by position\n";
2904 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
2906 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
2907 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
2908 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
2909 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
2914 bool add_mrv_selection = false;
2916 if (_selection.empty()) {
2917 add_mrv_selection = true;
2920 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
2921 if (_selection.insert (*i).second) {
2922 (*i)->set_selected (true);
2926 if (add_mrv_selection) {
2927 PublicEditor& editor (trackview.editor());
2928 editor.get_selection().add (this);
2933 MidiRegionView::color_handler ()
2935 RegionView::color_handler ();
2937 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2938 (*i)->set_selected ((*i)->selected()); // will change color
2941 /* XXX probably more to do here */
2945 MidiRegionView::enable_display (bool yn)
2947 RegionView::enable_display (yn);