Add a couple of missing attach_buffers() calls after _ports has been changed. I...
[ardour2.git] / gtk2_ardour / midi_region_view.cc
blobeb0f87e117aea7aeba675499a7d93a980d139c67
1 /*
2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
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.
20 #include <cmath>
21 #include <cassert>
22 #include <algorithm>
23 #include <ostream>
25 #include <gtkmm.h>
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/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas_patch_change.h"
52 #include "debug.h"
53 #include "editor.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
56 #include "keyboard.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "note_player.h"
65 #include "public_editor.h"
66 #include "rgb_macros.h"
67 #include "selection.h"
68 #include "simpleline.h"
69 #include "streamview.h"
70 #include "utils.h"
71 #include "mouse_cursors.h"
72 #include "patch_change_dialog.h"
73 #include "verbose_cursor.h"
75 #include "i18n.h"
77 using namespace ARDOUR;
78 using namespace PBD;
79 using namespace Editing;
80 using namespace ArdourCanvas;
81 using Gtkmm2ext::Keyboard;
83 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
84 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
85 : RegionView (parent, tv, r, spu, basic_color)
86 , _force_channel(-1)
87 , _last_channel_selection(0xFFFF)
88 , _current_range_min(0)
89 , _current_range_max(0)
90 , _model_name(string())
91 , _custom_device_mode(string())
92 , _active_notes(0)
93 , _note_group(new ArdourCanvas::Group(*group))
94 , _note_diff_command (0)
95 , _ghost_note(0)
96 , _drag_rect (0)
97 , _step_edit_cursor (0)
98 , _step_edit_cursor_width (1.0)
99 , _step_edit_cursor_position (0.0)
100 , _channel_selection_scoped_note (0)
101 , _temporary_note_group (0)
102 , _mouse_state(None)
103 , _pressed_button(0)
104 , _sort_needed (true)
105 , _optimization_iterator (_events.end())
106 , _list_editor (0)
107 , _no_sound_notes (false)
108 , _last_event_x (0)
109 , _last_event_y (0)
110 , _pre_enter_cursor (0)
112 _note_group->raise_to_top();
113 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
115 connect_to_diskstream ();
118 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
119 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
120 TimeAxisViewItem::Visibility visibility)
121 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
122 , _force_channel(-1)
123 , _last_channel_selection(0xFFFF)
124 , _model_name(string())
125 , _custom_device_mode(string())
126 , _active_notes(0)
127 , _note_group(new ArdourCanvas::Group(*parent))
128 , _note_diff_command (0)
129 , _ghost_note(0)
130 , _drag_rect (0)
131 , _step_edit_cursor (0)
132 , _step_edit_cursor_width (1.0)
133 , _step_edit_cursor_position (0.0)
134 , _channel_selection_scoped_note (0)
135 , _temporary_note_group (0)
136 , _mouse_state(None)
137 , _pressed_button(0)
138 , _sort_needed (true)
139 , _optimization_iterator (_events.end())
140 , _list_editor (0)
141 , _no_sound_notes (false)
142 , _last_event_x (0)
143 , _last_event_y (0)
145 _note_group->raise_to_top();
146 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
148 connect_to_diskstream ();
151 MidiRegionView::MidiRegionView (const MidiRegionView& other)
152 : sigc::trackable(other)
153 , RegionView (other)
154 , _force_channel(-1)
155 , _last_channel_selection(0xFFFF)
156 , _model_name(string())
157 , _custom_device_mode(string())
158 , _active_notes(0)
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
160 , _note_diff_command (0)
161 , _ghost_note(0)
162 , _drag_rect (0)
163 , _step_edit_cursor (0)
164 , _step_edit_cursor_width (1.0)
165 , _step_edit_cursor_position (0.0)
166 , _channel_selection_scoped_note (0)
167 , _temporary_note_group (0)
168 , _mouse_state(None)
169 , _pressed_button(0)
170 , _sort_needed (true)
171 , _optimization_iterator (_events.end())
172 , _list_editor (0)
173 , _no_sound_notes (false)
174 , _last_event_x (0)
175 , _last_event_y (0)
177 Gdk::Color c;
178 int r,g,b,a;
180 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
181 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
183 init (c, false);
186 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
187 : RegionView (other, boost::shared_ptr<Region> (region))
188 , _force_channel(-1)
189 , _last_channel_selection(0xFFFF)
190 , _model_name(string())
191 , _custom_device_mode(string())
192 , _active_notes(0)
193 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
194 , _note_diff_command (0)
195 , _ghost_note(0)
196 , _drag_rect (0)
197 , _step_edit_cursor (0)
198 , _step_edit_cursor_width (1.0)
199 , _step_edit_cursor_position (0.0)
200 , _channel_selection_scoped_note (0)
201 , _temporary_note_group (0)
202 , _mouse_state(None)
203 , _pressed_button(0)
204 , _sort_needed (true)
205 , _optimization_iterator (_events.end())
206 , _list_editor (0)
207 , _no_sound_notes (false)
208 , _last_event_x (0)
209 , _last_event_y (0)
211 Gdk::Color c;
212 int r,g,b,a;
214 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
215 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
217 init (c, true);
220 void
221 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
223 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
225 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
226 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
227 gui_context());
229 if (wfd) {
230 midi_region()->midi_source(0)->load_model();
233 _model = midi_region()->midi_source(0)->model();
234 _enable_display = false;
236 RegionView::init (basic_color, false);
238 compute_colors (basic_color);
240 set_height (trackview.current_height());
242 region_muted ();
243 region_sync_changed ();
244 region_resized (ARDOUR::bounds_change);
245 region_locked ();
247 reset_width_dependent_items (_pixel_width);
249 set_colors ();
251 _enable_display = true;
252 if (_model) {
253 if (wfd) {
254 display_model (_model);
258 group->raise_to_top();
259 group->signal_event().connect(
260 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
262 midi_view()->signal_channel_mode_changed().connect(
263 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
265 midi_view()->signal_midi_patch_settings_changed().connect(
266 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
268 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
269 ui_bind(&MidiRegionView::snap_changed, this),
270 gui_context());
272 connect_to_diskstream ();
275 void
276 MidiRegionView::connect_to_diskstream ()
278 midi_view()->midi_track()->DataRecorded.connect(
279 *this, invalidator(*this),
280 ui_bind(&MidiRegionView::data_recorded, this, _1, _2),
281 gui_context());
284 bool
285 MidiRegionView::canvas_event(GdkEvent* ev)
287 switch (ev->type) {
288 case GDK_ENTER_NOTIFY:
289 case GDK_LEAVE_NOTIFY:
290 _last_event_x = ev->crossing.x;
291 _last_event_y = ev->crossing.y;
292 break;
293 case GDK_MOTION_NOTIFY:
294 _last_event_x = ev->motion.x;
295 _last_event_y = ev->motion.y;
296 break;
297 default:
298 break;
301 if (!trackview.editor().internal_editing()) {
302 return false;
305 switch (ev->type) {
306 case GDK_SCROLL:
307 return scroll (&ev->scroll);
309 case GDK_KEY_PRESS:
310 return key_press (&ev->key);
312 case GDK_KEY_RELEASE:
313 return key_release (&ev->key);
315 case GDK_BUTTON_PRESS:
316 return button_press (&ev->button);
318 case GDK_2BUTTON_PRESS:
319 return true;
321 case GDK_BUTTON_RELEASE:
322 return button_release (&ev->button);
324 case GDK_ENTER_NOTIFY:
325 return enter_notify (&ev->crossing);
327 case GDK_LEAVE_NOTIFY:
328 return leave_notify (&ev->crossing);
330 case GDK_MOTION_NOTIFY:
331 return motion (&ev->motion);
333 default:
334 break;
337 return false;
340 void
341 MidiRegionView::remove_ghost_note ()
343 delete _ghost_note;
344 _ghost_note = 0;
347 bool
348 MidiRegionView::enter_notify (GdkEventCrossing* ev)
350 trackview.editor().MouseModeChanged.connect (
351 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
354 Keyboard::magic_widget_grab_focus();
355 group->grab_focus();
357 if (trackview.editor().current_mouse_mode() == MouseRange) {
358 create_ghost_note (ev->x, ev->y);
361 return false;
364 bool
365 MidiRegionView::leave_notify (GdkEventCrossing*)
367 _mouse_mode_connection.disconnect ();
369 trackview.editor().verbose_cursor()->hide ();
370 remove_ghost_note ();
371 return false;
374 void
375 MidiRegionView::mouse_mode_changed ()
377 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
378 create_ghost_note (_last_event_x, _last_event_y);
379 } else {
380 remove_ghost_note ();
381 trackview.editor().verbose_cursor()->hide ();
385 bool
386 MidiRegionView::button_press (GdkEventButton* ev)
388 if (ev->button != 1) {
389 return false;
392 _last_x = ev->x;
393 _last_y = ev->y;
395 group->w2i (_last_x, _last_y);
397 if (_mouse_state != SelectTouchDragging) {
399 _pressed_button = ev->button;
400 _mouse_state = Pressed;
402 return true;
405 _pressed_button = ev->button;
407 return true;
410 bool
411 MidiRegionView::button_release (GdkEventButton* ev)
413 double event_x, event_y;
415 if (ev->button != 1) {
416 return false;
419 event_x = ev->x;
420 event_y = ev->y;
422 group->w2i(event_x, event_y);
423 group->ungrab(ev->time);
425 switch (_mouse_state) {
426 case Pressed: // Clicked
428 switch (trackview.editor().current_mouse_mode()) {
429 case MouseObject:
430 case MouseTimeFX:
432 clear_selection();
434 if (Keyboard::is_insert_note_event(ev)) {
436 double event_x, event_y;
438 event_x = ev->x;
439 event_y = ev->y;
440 group->w2i(event_x, event_y);
442 bool success;
443 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
445 if (!success) {
446 beats = 1;
449 create_note_at (event_x, event_y, beats, true);
452 break;
454 case MouseRange:
456 bool success;
457 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
459 if (!success) {
460 beats = 1;
463 create_note_at (event_x, event_y, beats, true);
465 break;
467 default:
468 break;
471 _mouse_state = None;
472 break;
474 case SelectRectDragging: // Select drag done
476 _mouse_state = None;
477 delete _drag_rect;
478 _drag_rect = 0;
479 break;
481 case AddDragging: // Add drag done
483 _mouse_state = None;
485 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
487 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
489 const double x = _drag_rect->property_x1();
490 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
492 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), true);
496 delete _drag_rect;
497 _drag_rect = 0;
499 create_ghost_note (ev->x, ev->y);
501 default:
502 break;
505 return false;
508 bool
509 MidiRegionView::motion (GdkEventMotion* ev)
511 double event_x, event_y;
512 framepos_t event_frame = 0;
514 event_x = ev->x;
515 event_y = ev->y;
516 group->w2i(event_x, event_y);
518 // convert event_x to global frame
519 event_frame = snap_pixel_to_frame (event_x);
521 if (!_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
522 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
523 && _mouse_state != AddDragging) {
525 create_ghost_note (ev->x, ev->y);
526 } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
527 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
529 update_ghost_note (ev->x, ev->y);
530 } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange) {
532 delete _ghost_note;
533 _ghost_note = 0;
535 trackview.editor().verbose_cursor()->hide ();
536 } else if (_ghost_note && trackview.editor().current_mouse_mode() == MouseRange) {
537 update_ghost_note (ev->x, ev->y);
540 /* any motion immediately hides velocity text that may have been visible */
542 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
543 (*i)->hide_velocity ();
546 switch (_mouse_state) {
547 case Pressed: // Maybe start a drag, if we've moved a bit
549 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
550 /* no appreciable movement since the button was pressed */
551 return false;
554 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject
555 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
556 // Select drag start
558 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
559 Gdk::Cursor(Gdk::FLEUR), ev->time);
561 _last_x = event_x;
562 _last_y = event_y;
563 _drag_start_x = event_x;
564 _drag_start_y = event_y;
566 _drag_rect = new ArdourCanvas::SimpleRect(*group);
567 _drag_rect->property_x1() = event_x;
568 _drag_rect->property_y1() = event_y;
569 _drag_rect->property_x2() = event_x;
570 _drag_rect->property_y2() = event_y;
571 _drag_rect->property_outline_what() = 0xFF;
572 _drag_rect->property_outline_color_rgba()
573 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
574 _drag_rect->property_fill_color_rgba()
575 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
577 _mouse_state = SelectRectDragging;
578 return true;
580 } else if (trackview.editor().internal_editing()) {
581 // Add note drag start
583 delete _ghost_note;
584 _ghost_note = 0;
586 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
587 Gdk::Cursor(Gdk::FLEUR), ev->time);
589 _last_x = event_x;
590 _last_y = event_y;
591 _drag_start_x = event_x;
592 _drag_start_y = event_y;
594 _drag_rect = new ArdourCanvas::SimpleRect(*group);
595 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
597 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
598 midi_stream_view()->y_to_note(event_y));
599 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
600 _drag_rect->property_y2() = _drag_rect->property_y1()
601 + floor(midi_stream_view()->note_height());
602 _drag_rect->property_outline_what() = 0xFF;
603 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
604 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
606 _mouse_state = AddDragging;
608 if (_ghost_note) {
610 delete _ghost_note;
611 _ghost_note = 0;
613 trackview.editor().verbose_cursor()->hide ();
616 return true;
619 return false;
621 case SelectRectDragging: // Select drag motion
622 case AddDragging: // Add note drag motion
624 if (ev->is_hint) {
625 int t_x;
626 int t_y;
627 GdkModifierType state;
628 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
629 event_x = t_x;
630 event_y = t_y;
633 if (_mouse_state == AddDragging) {
634 event_x = trackview.editor().frame_to_pixel(event_frame);
637 if (_drag_rect) {
639 if (event_x > _drag_start_x) {
640 _drag_rect->property_x2() = event_x;
642 else {
643 _drag_rect->property_x1() = event_x;
647 if (_drag_rect && _mouse_state == SelectRectDragging) {
649 if (event_y > _drag_start_y) {
650 _drag_rect->property_y2() = event_y;
652 else {
653 _drag_rect->property_y1() = event_y;
656 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
659 _last_x = event_x;
660 _last_y = event_y;
662 case SelectTouchDragging:
663 return false;
665 default:
666 break;
669 return false;
673 bool
674 MidiRegionView::scroll (GdkEventScroll* ev)
676 if (_selection.empty()) {
677 return false;
680 trackview.editor().verbose_cursor()->hide ();
682 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
684 if (ev->direction == GDK_SCROLL_UP) {
685 change_velocities (true, fine, false);
686 } else if (ev->direction == GDK_SCROLL_DOWN) {
687 change_velocities (false, fine, false);
689 return true;
692 bool
693 MidiRegionView::key_press (GdkEventKey* ev)
695 /* since GTK bindings are generally activated on press, and since
696 detectable auto-repeat is the name of the game and only sends
697 repeated presses, carry out key actions at key press, not release.
700 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
701 _mouse_state = SelectTouchDragging;
702 return true;
704 } else if (ev->keyval == GDK_Escape) {
705 clear_selection();
706 _mouse_state = None;
708 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
710 bool start = (ev->keyval == GDK_comma);
711 bool end = (ev->keyval == GDK_period);
712 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
713 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
715 change_note_lengths (fine, shorter, 0.0, start, end);
717 return true;
719 } else if (ev->keyval == GDK_Delete) {
721 delete_selection();
722 return true;
724 } else if (ev->keyval == GDK_Tab) {
726 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
727 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
728 } else {
729 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
731 return true;
733 } else if (ev->keyval == GDK_ISO_Left_Tab) {
735 /* Shift-TAB generates ISO Left Tab, for some reason */
737 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
738 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
739 } else {
740 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
742 return true;
746 } else if (ev->keyval == GDK_Up) {
748 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
749 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
751 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
752 change_velocities (true, fine, allow_smush);
753 } else {
754 transpose (true, fine, allow_smush);
756 return true;
758 } else if (ev->keyval == GDK_Down) {
760 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
761 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
763 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
764 change_velocities (false, fine, allow_smush);
765 } else {
766 transpose (false, fine, allow_smush);
768 return true;
770 } else if (ev->keyval == GDK_Left) {
772 nudge_notes (false);
773 return true;
775 } else if (ev->keyval == GDK_Right) {
777 nudge_notes (true);
778 return true;
780 } else if (ev->keyval == GDK_Control_L) {
781 return true;
783 } else if (ev->keyval == GDK_c) {
784 channel_edit ();
785 return true;
788 return false;
791 bool
792 MidiRegionView::key_release (GdkEventKey* ev)
794 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
795 _mouse_state = None;
796 return true;
798 return false;
801 void
802 MidiRegionView::channel_edit ()
804 bool first = true;
805 uint8_t current_channel;
807 if (_selection.empty()) {
808 return;
811 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
812 if (first) {
813 current_channel = (*i)->note()->channel ();
814 first = false;
818 MidiChannelDialog channel_dialog (current_channel);
819 int ret = channel_dialog.run ();
821 switch (ret) {
822 case Gtk::RESPONSE_OK:
823 break;
824 default:
825 return;
828 uint8_t new_channel = channel_dialog.active_channel ();
830 start_note_diff_command (_("channel edit"));
832 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
833 Selection::iterator next = i;
834 ++next;
835 change_note_channel (*i, new_channel);
836 i = next;
839 apply_diff ();
842 void
843 MidiRegionView::show_list_editor ()
845 if (!_list_editor) {
846 _list_editor = new MidiListEditor (trackview.session(), midi_region());
848 _list_editor->present ();
851 /** Add a note to the model, and the view, at a canvas (click) coordinate.
852 * \param x horizontal position in pixels
853 * \param y vertical position in pixels
854 * \param length duration of the note in beats, which will be snapped to the grid
855 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
857 void
858 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
860 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
861 MidiStreamView* const view = mtv->midi_view();
863 double note = view->y_to_note(y);
865 assert(note >= 0.0);
866 assert(note <= 127.0);
868 // Start of note in frames relative to region start
869 framepos_t const start_frames = snap_pixel_to_frame (x);
870 assert(start_frames >= 0);
872 // Snap length
873 length = frames_to_beats(
874 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
876 assert (length != 0);
878 if (sh) {
879 length = frames_to_beats (beats_to_frames (length) - 1);
882 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
883 frames_to_beats(start_frames + _region->start()), length,
884 (uint8_t)note, 0x40));
886 if (_model->contains (new_note)) {
887 return;
890 view->update_note_range(new_note->note());
892 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
893 cmd->add (new_note);
894 _model->apply_command(*trackview.session(), cmd);
896 play_midi_note (new_note);
899 void
900 MidiRegionView::clear_events()
902 clear_selection();
904 MidiGhostRegion* gr;
905 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
906 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
907 gr->clear_events();
911 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
912 delete *i;
915 _events.clear();
916 _patch_changes.clear();
917 _sys_exes.clear();
918 _optimization_iterator = _events.end();
921 void
922 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
924 _model = model;
926 content_connection.disconnect ();
927 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
929 clear_events ();
931 if (_enable_display) {
932 redisplay_model();
936 void
937 MidiRegionView::start_note_diff_command (string name)
939 if (!_note_diff_command) {
940 _note_diff_command = _model->new_note_diff_command (name);
944 void
945 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
947 if (_note_diff_command) {
948 _note_diff_command->add (note);
950 if (selected) {
951 _marked_for_selection.insert(note);
953 if (show_velocity) {
954 _marked_for_velocity.insert(note);
958 void
959 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
961 if (_note_diff_command && ev->note()) {
962 _note_diff_command->remove(ev->note());
966 void
967 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
968 MidiModel::NoteDiffCommand::Property property,
969 uint8_t val)
971 if (_note_diff_command) {
972 _note_diff_command->change (ev->note(), property, val);
976 void
977 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
978 MidiModel::NoteDiffCommand::Property property,
979 Evoral::MusicalTime val)
981 if (_note_diff_command) {
982 _note_diff_command->change (ev->note(), property, val);
986 void
987 MidiRegionView::apply_diff (bool as_subcommand)
989 bool add_or_remove;
991 if (!_note_diff_command) {
992 return;
995 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
996 // Mark all selected notes for selection when model reloads
997 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
998 _marked_for_selection.insert((*i)->note());
1002 if (as_subcommand) {
1003 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1004 } else {
1005 _model->apply_command (*trackview.session(), _note_diff_command);
1008 _note_diff_command = 0;
1009 midi_view()->midi_track()->playlist_modified();
1011 if (add_or_remove) {
1012 _marked_for_selection.clear();
1015 _marked_for_velocity.clear();
1018 void
1019 MidiRegionView::abort_command()
1021 delete _note_diff_command;
1022 _note_diff_command = 0;
1023 clear_selection();
1026 CanvasNoteEvent*
1027 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1029 if (_optimization_iterator != _events.end()) {
1030 ++_optimization_iterator;
1033 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1034 return *_optimization_iterator;
1037 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1038 if ((*_optimization_iterator)->note() == note) {
1039 return *_optimization_iterator;
1043 return 0;
1046 void
1047 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1049 MidiModel::Notes notes;
1050 _model->get_notes (notes, op, val, chan_mask);
1052 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1053 CanvasNoteEvent* cne = find_canvas_note (*n);
1054 if (cne) {
1055 e.push_back (cne);
1060 void
1061 MidiRegionView::redisplay_model()
1063 // Don't redisplay the model if we're currently recording and displaying that
1064 if (_active_notes) {
1065 return;
1068 if (!_model) {
1069 return;
1072 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1073 (*i)->invalidate ();
1076 MidiModel::ReadLock lock(_model->read_lock());
1078 MidiModel::Notes& notes (_model->notes());
1079 _optimization_iterator = _events.begin();
1081 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1083 boost::shared_ptr<NoteType> note (*n);
1084 CanvasNoteEvent* cne;
1085 bool visible;
1087 if (note_in_region_range (note, visible)) {
1089 if ((cne = find_canvas_note (note)) != 0) {
1091 cne->validate ();
1093 CanvasNote* cn;
1094 CanvasHit* ch;
1096 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1097 update_note (cn);
1098 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1099 update_hit (ch);
1102 if (visible) {
1103 cne->show ();
1104 } else {
1105 cne->hide ();
1108 } else {
1110 add_note (note, visible);
1113 } else {
1115 if ((cne = find_canvas_note (note)) != 0) {
1116 cne->validate ();
1117 cne->hide ();
1123 /* remove note items that are no longer valid */
1125 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1126 if (!(*i)->valid ()) {
1127 delete *i;
1128 i = _events.erase (i);
1129 } else {
1130 ++i;
1134 _patch_changes.clear();
1135 _sys_exes.clear();
1137 display_sysexes();
1138 display_patch_changes ();
1140 _marked_for_selection.clear ();
1141 _marked_for_velocity.clear ();
1143 /* we may have caused _events to contain things out of order (e.g. if a note
1144 moved earlier or later). we don't generally need them in time order, but
1145 make a note that a sort is required for those cases that require it.
1148 _sort_needed = true;
1151 void
1152 MidiRegionView::display_patch_changes ()
1154 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1155 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1157 for (uint8_t i = 0; i < 16; ++i) {
1158 if (chn_mask & (1<<i)) {
1159 display_patch_changes_on_channel (i);
1164 void
1165 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1167 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1169 if ((*i)->channel() != channel) {
1170 continue;
1173 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1175 boost::shared_ptr<MIDI::Name::Patch> patch =
1176 MIDI::Name::MidiPatchManager::instance().find_patch(
1177 _model_name, _custom_device_mode, channel, patch_key);
1179 if (patch != 0) {
1180 add_canvas_patch_change (*i, patch->name());
1181 } else {
1182 char buf[16];
1183 /* program and bank numbers are zero-based: convert to one-based */
1184 snprintf (buf, 16, "%d\n%d", (*i)->program() + 1, (*i)->bank() + 1);
1185 add_canvas_patch_change (*i, buf);
1190 void
1191 MidiRegionView::display_sysexes()
1193 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1194 Evoral::MusicalTime time = (*i)->time();
1195 assert(time >= 0);
1197 ostringstream str;
1198 str << hex;
1199 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1200 str << int((*i)->buffer()[b]);
1201 if (b != (*i)->size() -1) {
1202 str << " ";
1205 string text = str.str();
1207 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1209 double height = midi_stream_view()->contents_height();
1211 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1212 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1214 // Show unless patch change is beyond the region bounds
1215 if (time - _region->start() >= _region->length() || time < _region->start()) {
1216 sysex->hide();
1217 } else {
1218 sysex->show();
1221 _sys_exes.push_back(sysex);
1226 MidiRegionView::~MidiRegionView ()
1228 in_destructor = true;
1230 trackview.editor().verbose_cursor()->hide ();
1232 note_delete_connection.disconnect ();
1234 delete _list_editor;
1236 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1238 if (_active_notes) {
1239 end_write();
1242 _selection.clear();
1243 clear_events();
1245 delete _note_group;
1246 delete _note_diff_command;
1247 delete _step_edit_cursor;
1248 delete _temporary_note_group;
1251 void
1252 MidiRegionView::region_resized (const PropertyChange& what_changed)
1254 RegionView::region_resized(what_changed);
1256 if (what_changed.contains (ARDOUR::Properties::position)) {
1257 set_duration(_region->length(), 0);
1258 if (_enable_display) {
1259 redisplay_model();
1264 void
1265 MidiRegionView::reset_width_dependent_items (double pixel_width)
1267 RegionView::reset_width_dependent_items(pixel_width);
1268 assert(_pixel_width == pixel_width);
1270 if (_enable_display) {
1271 redisplay_model();
1274 move_step_edit_cursor (_step_edit_cursor_position);
1275 set_step_edit_cursor_width (_step_edit_cursor_width);
1278 void
1279 MidiRegionView::set_height (double height)
1281 static const double FUDGE = 2.0;
1282 const double old_height = _height;
1283 RegionView::set_height(height);
1284 _height = height - FUDGE;
1286 apply_note_range(midi_stream_view()->lowest_note(),
1287 midi_stream_view()->highest_note(),
1288 height != old_height + FUDGE);
1290 if (name_pixbuf) {
1291 name_pixbuf->raise_to_top();
1294 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1295 (*x)->set_height (midi_stream_view()->contents_height());
1298 if (_step_edit_cursor) {
1299 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1304 /** Apply the current note range from the stream view
1305 * by repositioning/hiding notes as necessary
1307 void
1308 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1310 if (!_enable_display) {
1311 return;
1314 if (!force && _current_range_min == min && _current_range_max == max) {
1315 return;
1318 _current_range_min = min;
1319 _current_range_max = max;
1321 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1322 CanvasNoteEvent* event = *i;
1323 boost::shared_ptr<NoteType> note (event->note());
1325 if (note->note() < _current_range_min ||
1326 note->note() > _current_range_max) {
1327 event->hide();
1328 } else {
1329 event->show();
1332 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1334 const double y1 = midi_stream_view()->note_to_y(note->note());
1335 const double y2 = y1 + floor(midi_stream_view()->note_height());
1337 cnote->property_y1() = y1;
1338 cnote->property_y2() = y2;
1340 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1342 const double diamond_size = update_hit (chit);
1344 chit->set_height (diamond_size);
1349 GhostRegion*
1350 MidiRegionView::add_ghost (TimeAxisView& tv)
1352 CanvasNote* note;
1354 double unit_position = _region->position () / samples_per_unit;
1355 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1356 MidiGhostRegion* ghost;
1358 if (mtv && mtv->midi_view()) {
1359 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1360 to allow having midi notes on top of note lines and waveforms.
1362 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1363 } else {
1364 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1367 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1368 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1369 ghost->add_note(note);
1373 ghost->set_height ();
1374 ghost->set_duration (_region->length() / samples_per_unit);
1375 ghosts.push_back (ghost);
1377 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1379 return ghost;
1383 /** Begin tracking note state for successive calls to add_event
1385 void
1386 MidiRegionView::begin_write()
1388 assert(!_active_notes);
1389 _active_notes = new CanvasNote*[128];
1390 for (unsigned i=0; i < 128; ++i) {
1391 _active_notes[i] = 0;
1396 /** Destroy note state for add_event
1398 void
1399 MidiRegionView::end_write()
1401 delete[] _active_notes;
1402 _active_notes = 0;
1403 _marked_for_selection.clear();
1404 _marked_for_velocity.clear();
1408 /** Resolve an active MIDI note (while recording).
1410 void
1411 MidiRegionView::resolve_note(uint8_t note, double end_time)
1413 if (midi_view()->note_mode() != Sustained) {
1414 return;
1417 if (_active_notes && _active_notes[note]) {
1419 const framepos_t end_time_frames = beats_to_frames(end_time);
1421 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1422 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1423 _active_notes[note] = 0;
1428 /** Extend active notes to rightmost edge of region (if length is changed)
1430 void
1431 MidiRegionView::extend_active_notes()
1433 if (!_active_notes) {
1434 return;
1437 for (unsigned i=0; i < 128; ++i) {
1438 if (_active_notes[i]) {
1439 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1445 void
1446 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1448 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1449 return;
1452 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1454 if (!route_ui || !route_ui->midi_track()) {
1455 return;
1458 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1459 np->add (note);
1460 np->play ();
1463 void
1464 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1466 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1467 return;
1470 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1472 if (!route_ui || !route_ui->midi_track()) {
1473 return;
1476 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1478 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1479 np->add (*n);
1482 np->play ();
1486 bool
1487 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1489 const framepos_t note_start_frames = beats_to_frames(note->time());
1491 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1492 (note_start_frames < _region->start());
1494 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1495 (note->note() <= midi_stream_view()->highest_note());
1497 return !outside;
1500 /** Update a canvas note's size from its model note.
1501 * @param ev Canvas note to update.
1502 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1504 void
1505 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1507 boost::shared_ptr<NoteType> note = ev->note();
1509 const framepos_t note_start_frames = beats_to_frames(note->time());
1511 /* trim note display to not overlap the end of its region */
1512 const framepos_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length());
1514 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1515 const double y1 = midi_stream_view()->note_to_y(note->note());
1516 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1518 ev->property_x1() = x;
1519 ev->property_y1() = y1;
1521 if (note->length() > 0) {
1522 ev->property_x2() = note_endpixel;
1523 } else {
1524 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1527 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1529 if (note->length() == 0) {
1530 if (_active_notes) {
1531 assert(note->note() < 128);
1532 // If this note is already active there's a stuck note,
1533 // finish the old note rectangle
1534 if (_active_notes[note->note()]) {
1535 CanvasNote* const old_rect = _active_notes[note->note()];
1536 boost::shared_ptr<NoteType> old_note = old_rect->note();
1537 old_rect->property_x2() = x;
1538 old_rect->property_outline_what() = (guint32) 0xF;
1540 _active_notes[note->note()] = ev;
1542 /* outline all but right edge */
1543 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1544 } else {
1545 /* outline all edges */
1546 ev->property_outline_what() = (guint32) 0xF;
1549 if (update_ghost_regions) {
1550 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1551 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1552 if (gr) {
1553 gr->update_note (ev);
1559 double
1560 MidiRegionView::update_hit (CanvasHit* ev)
1562 boost::shared_ptr<NoteType> note = ev->note();
1564 const framepos_t note_start_frames = beats_to_frames(note->time());
1565 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1566 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1567 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1569 ev->move_to (x, y);
1571 return diamond_size;
1574 /** Add a MIDI note to the view (with length).
1576 * If in sustained mode, notes with length 0 will be considered active
1577 * notes, and resolve_note should be called when the corresponding note off
1578 * event arrives, to properly display the note.
1580 void
1581 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1583 CanvasNoteEvent* event = 0;
1585 assert(note->time() >= 0);
1586 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1588 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1590 if (midi_view()->note_mode() == Sustained) {
1592 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1594 update_note (ev_rect);
1596 event = ev_rect;
1598 MidiGhostRegion* gr;
1600 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1601 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1602 gr->add_note(ev_rect);
1606 } else if (midi_view()->note_mode() == Percussive) {
1608 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1610 CanvasHit* ev_diamond = new CanvasHit(*this, *_note_group, diamond_size, note);
1612 update_hit (ev_diamond);
1614 event = ev_diamond;
1616 } else {
1617 event = 0;
1620 if (event) {
1621 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1622 note_selected(event, true);
1625 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1626 event->show_velocity();
1629 event->on_channel_selection_change(_last_channel_selection);
1630 _events.push_back(event);
1632 if (visible) {
1633 event->show();
1634 } else {
1635 event->hide ();
1639 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1640 MidiStreamView* const view = mtv->midi_view();
1642 view->update_note_range(note->note());
1645 void
1646 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1647 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1649 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1651 /* potentially extend region to hold new note */
1653 framepos_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1654 framepos_t region_end = _region->position() + _region->length() - 1;
1656 if (end_frame > region_end) {
1657 _region->set_length (end_frame - _region->position());
1660 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1661 MidiStreamView* const view = mtv->midi_view();
1663 view->update_note_range(new_note->note());
1665 _marked_for_selection.clear ();
1666 clear_selection ();
1668 start_note_diff_command (_("step add"));
1669 note_diff_add_note (new_note, true, false);
1670 apply_diff();
1672 // last_step_edit_note = new_note;
1675 void
1676 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1678 change_note_lengths (false, false, beats, false, true);
1681 void
1682 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1684 assert (patch->time() >= 0);
1686 const double x = trackview.editor().frame_to_pixel (beats_to_frames (patch->time()));
1688 double const height = midi_stream_view()->contents_height();
1690 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1691 new CanvasPatchChange(*this, *_note_group,
1692 displaytext,
1693 height,
1694 x, 1.0,
1695 _model_name,
1696 _custom_device_mode,
1697 patch)
1700 // Show unless patch change is beyond the region bounds
1701 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1702 patch_change->hide();
1703 } else {
1704 patch_change->show();
1707 _patch_changes.push_back (patch_change);
1710 void
1711 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1713 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1714 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1715 ++i;
1718 if (i != _model->patch_changes().end()) {
1719 key.msb = (*i)->bank_msb ();
1720 key.lsb = (*i)->bank_lsb ();
1721 key.program_number = (*i)->program ();
1722 } else {
1723 key.msb = key.lsb = key.program_number = 0;
1726 assert (key.is_sane());
1730 void
1731 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1733 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1735 if (pc.patch()->program() != new_patch.program_number) {
1736 c->change_program (pc.patch (), new_patch.program_number);
1739 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1740 if (pc.patch()->bank() != new_bank) {
1741 c->change_bank (pc.patch (), new_bank);
1744 _model->apply_command (*trackview.session(), c);
1746 _patch_changes.clear ();
1747 display_patch_changes ();
1750 void
1751 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1753 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1755 if (old_change->time() != new_change.time()) {
1756 c->change_time (old_change, new_change.time());
1759 if (old_change->channel() != new_change.channel()) {
1760 c->change_channel (old_change, new_change.channel());
1763 if (old_change->program() != new_change.program()) {
1764 c->change_program (old_change, new_change.program());
1767 if (old_change->bank() != new_change.bank()) {
1768 c->change_bank (old_change, new_change.bank());
1771 _model->apply_command (*trackview.session(), c);
1773 _patch_changes.clear ();
1774 display_patch_changes ();
1777 /** Add a patch change to the region.
1778 * @param t Time in frames relative to region position
1779 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1780 * MidiTimeAxisView::get_channel_for_add())
1782 void
1783 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1785 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1787 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1788 c->add (MidiModel::PatchChangePtr (
1789 new Evoral::PatchChange<Evoral::MusicalTime> (
1790 frames_to_beats (t + midi_region()->start()), mtv->get_channel_for_add(), patch.program(), patch.bank()
1794 _model->apply_command (*trackview.session(), c);
1796 _patch_changes.clear ();
1797 display_patch_changes ();
1800 void
1801 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1803 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1804 c->change_time (pc.patch (), t);
1805 _model->apply_command (*trackview.session(), c);
1807 _patch_changes.clear ();
1808 display_patch_changes ();
1811 void
1812 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1814 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1815 c->remove (pc->patch ());
1816 _model->apply_command (*trackview.session(), c);
1818 _patch_changes.clear ();
1819 display_patch_changes ();
1822 void
1823 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1825 if (patch.patch()->program() < 127) {
1826 MIDI::Name::PatchPrimaryKey key;
1827 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1828 key.program_number++;
1829 change_patch_change (patch, key);
1833 void
1834 MidiRegionView::next_patch (CanvasPatchChange& patch)
1836 if (patch.patch()->program() > 0) {
1837 MIDI::Name::PatchPrimaryKey key;
1838 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1839 key.program_number--;
1840 change_patch_change (patch, key);
1844 void
1845 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1847 if (_selection.empty()) {
1848 return;
1851 _selection.erase (cne);
1854 void
1855 MidiRegionView::delete_selection()
1857 if (_selection.empty()) {
1858 return;
1861 start_note_diff_command (_("delete selection"));
1863 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1864 if ((*i)->selected()) {
1865 _note_diff_command->remove((*i)->note());
1869 _selection.clear();
1871 apply_diff ();
1874 void
1875 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1877 start_note_diff_command (_("delete note"));
1878 _note_diff_command->remove (n);
1879 apply_diff ();
1881 trackview.editor().verbose_cursor()->hide ();
1884 void
1885 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1887 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1888 if ((*i)->selected() && (*i) != ev) {
1889 (*i)->set_selected(false);
1890 (*i)->hide_velocity();
1894 _selection.clear();
1897 void
1898 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1900 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1901 if ((*i) != ev) {
1903 Selection::iterator tmp = i;
1904 ++tmp;
1906 (*i)->set_selected (false);
1907 _selection.erase (i);
1909 i = tmp;
1911 } else {
1912 ++i;
1916 /* don't bother with removing this regionview from the editor selection,
1917 since we're about to add another note, and thus put/keep this
1918 regionview in the editor selection.
1921 if (!ev->selected()) {
1922 add_to_selection (ev);
1926 void
1927 MidiRegionView::select_all_notes ()
1929 clear_selection ();
1931 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1932 add_to_selection (*i);
1936 void
1937 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1939 uint8_t low_note = 127;
1940 uint8_t high_note = 0;
1941 MidiModel::Notes& notes (_model->notes());
1942 _optimization_iterator = _events.begin();
1944 if (!add) {
1945 clear_selection ();
1948 if (extend && _selection.empty()) {
1949 extend = false;
1952 if (extend) {
1954 /* scan existing selection to get note range */
1956 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1957 if ((*i)->note()->note() < low_note) {
1958 low_note = (*i)->note()->note();
1960 if ((*i)->note()->note() > high_note) {
1961 high_note = (*i)->note()->note();
1965 low_note = min (low_note, notenum);
1966 high_note = max (high_note, notenum);
1969 _no_sound_notes = true;
1971 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1973 boost::shared_ptr<NoteType> note (*n);
1974 CanvasNoteEvent* cne;
1975 bool select = false;
1977 if (((1 << note->channel()) & channel_mask) != 0) {
1978 if (extend) {
1979 if ((note->note() >= low_note && note->note() <= high_note)) {
1980 select = true;
1982 } else if (note->note() == notenum) {
1983 select = true;
1987 if (select) {
1988 if ((cne = find_canvas_note (note)) != 0) {
1989 // extend is false because we've taken care of it,
1990 // since it extends by time range, not pitch.
1991 note_selected (cne, add, false);
1995 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1999 _no_sound_notes = false;
2002 void
2003 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2005 MidiModel::Notes& notes (_model->notes());
2006 _optimization_iterator = _events.begin();
2008 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2010 boost::shared_ptr<NoteType> note (*n);
2011 CanvasNoteEvent* cne;
2013 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2014 if ((cne = find_canvas_note (note)) != 0) {
2015 if (cne->selected()) {
2016 note_deselected (cne);
2017 } else {
2018 note_selected (cne, true, false);
2025 void
2026 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2028 if (!add) {
2029 clear_selection_except(ev);
2032 if (!extend) {
2034 if (!ev->selected()) {
2035 add_to_selection (ev);
2038 } else {
2039 /* find end of latest note selected, select all between that and the start of "ev" */
2041 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2042 Evoral::MusicalTime latest = 0;
2044 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2045 if ((*i)->note()->end_time() > latest) {
2046 latest = (*i)->note()->end_time();
2048 if ((*i)->note()->time() < earliest) {
2049 earliest = (*i)->note()->time();
2053 if (ev->note()->end_time() > latest) {
2054 latest = ev->note()->end_time();
2057 if (ev->note()->time() < earliest) {
2058 earliest = ev->note()->time();
2061 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2063 /* find notes entirely within OR spanning the earliest..latest range */
2065 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2066 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2067 add_to_selection (*i);
2074 void
2075 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2077 remove_from_selection (ev);
2080 void
2081 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
2083 if (x1 > x2) {
2084 swap (x1, x2);
2087 if (y1 > y2) {
2088 swap (y1, y2);
2091 // TODO: Make this faster by storing the last updated selection rect, and only
2092 // adjusting things that are in the area that appears/disappeared.
2093 // We probably need a tree to be able to find events in O(log(n)) time.
2095 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2097 /* check if any corner of the note is inside the rect
2099 Notes:
2100 1) this is computing "touched by", not "contained by" the rect.
2101 2) this does not require that events be sorted in time.
2104 const double ix1 = (*i)->x1();
2105 const double ix2 = (*i)->x2();
2106 const double iy1 = (*i)->y1();
2107 const double iy2 = (*i)->y2();
2109 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2110 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2111 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2112 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2114 // Inside rectangle
2115 if (!(*i)->selected()) {
2116 add_to_selection (*i);
2118 } else if ((*i)->selected()) {
2119 // Not inside rectangle
2120 remove_from_selection (*i);
2125 void
2126 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2128 Selection::iterator i = _selection.find (ev);
2130 if (i != _selection.end()) {
2131 _selection.erase (i);
2134 ev->set_selected (false);
2135 ev->hide_velocity ();
2137 if (_selection.empty()) {
2138 PublicEditor& editor (trackview.editor());
2139 editor.get_selection().remove (this);
2143 void
2144 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2146 bool add_mrv_selection = false;
2148 if (_selection.empty()) {
2149 add_mrv_selection = true;
2152 if (_selection.insert (ev).second) {
2153 ev->set_selected (true);
2154 play_midi_note ((ev)->note());
2157 if (add_mrv_selection) {
2158 PublicEditor& editor (trackview.editor());
2159 editor.get_selection().add (this);
2163 void
2164 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2166 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2167 PossibleChord to_play;
2168 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2170 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2171 if ((*i)->note()->time() < earliest) {
2172 earliest = (*i)->note()->time();
2176 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2177 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2178 to_play.push_back ((*i)->note());
2180 (*i)->move_event(dx, dy);
2183 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2185 if (to_play.size() > 1) {
2187 PossibleChord shifted;
2189 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2190 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2191 moved_note->set_note (moved_note->note() + cumulative_dy);
2192 shifted.push_back (moved_note);
2195 play_midi_chord (shifted);
2197 } else if (!to_play.empty()) {
2199 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2200 moved_note->set_note (moved_note->note() + cumulative_dy);
2201 play_midi_note (moved_note);
2206 void
2207 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2209 assert (!_selection.empty());
2211 uint8_t lowest_note_in_selection = 127;
2212 uint8_t highest_note_in_selection = 0;
2213 uint8_t highest_note_difference = 0;
2215 // find highest and lowest notes first
2217 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2218 uint8_t pitch = (*i)->note()->note();
2219 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2220 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2224 cerr << "dnote: " << (int) dnote << endl;
2225 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2226 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2227 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2228 << int(highest_note_in_selection) << endl;
2229 cerr << "selection size: " << _selection.size() << endl;
2230 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2233 // Make sure the note pitch does not exceed the MIDI standard range
2234 if (highest_note_in_selection + dnote > 127) {
2235 highest_note_difference = highest_note_in_selection - 127;
2238 start_note_diff_command (_("move notes"));
2240 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2242 Evoral::MusicalTime new_time = frames_to_beats (beats_to_frames ((*i)->note()->time()) + dt);
2244 if (new_time < 0) {
2245 continue;
2248 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2250 uint8_t original_pitch = (*i)->note()->note();
2251 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2253 // keep notes in standard midi range
2254 clamp_to_0_127(new_pitch);
2256 // keep original pitch if note is dragged outside valid midi range
2257 if ((original_pitch != 0 && new_pitch == 0)
2258 || (original_pitch != 127 && new_pitch == 127)) {
2259 new_pitch = original_pitch;
2262 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2263 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2265 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2268 apply_diff();
2270 // care about notes being moved beyond the upper/lower bounds on the canvas
2271 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2272 highest_note_in_selection > midi_stream_view()->highest_note()) {
2273 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2277 framepos_t
2278 MidiRegionView::snap_pixel_to_frame(double x)
2280 PublicEditor& editor (trackview.editor());
2281 return snap_frame_to_frame (editor.pixel_to_frame (x));
2284 /** Snap a frame offset within our region using the current snap settings.
2285 * @param x Frame offset from this region's position.
2286 * @return Snapped frame offset from this region's position.
2288 frameoffset_t
2289 MidiRegionView::snap_frame_to_frame (frameoffset_t x)
2291 PublicEditor& editor = trackview.editor();
2293 /* x is region relative, convert it to global absolute frames */
2294 framepos_t const session_frame = x + _region->position();
2296 /* try a snap in either direction */
2297 framepos_t frame = session_frame;
2298 editor.snap_to (frame, 0);
2300 /* if we went off the beginning of the region, snap forwards */
2301 if (frame < _region->position ()) {
2302 frame = session_frame;
2303 editor.snap_to (frame, 1);
2306 /* back to region relative */
2307 return frame - _region->position();
2310 double
2311 MidiRegionView::snap_to_pixel(double x)
2313 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2316 double
2317 MidiRegionView::get_position_pixels()
2319 framepos_t region_frame = get_position();
2320 return trackview.editor().frame_to_pixel(region_frame);
2323 double
2324 MidiRegionView::get_end_position_pixels()
2326 framepos_t frame = get_position() + get_duration ();
2327 return trackview.editor().frame_to_pixel(frame);
2330 framepos_t
2331 MidiRegionView::beats_to_frames(double beats) const
2333 return _time_converter.to(beats);
2336 double
2337 MidiRegionView::frames_to_beats(framepos_t frames) const
2339 return _time_converter.from(frames);
2342 void
2343 MidiRegionView::begin_resizing (bool /*at_front*/)
2345 _resize_data.clear();
2347 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2348 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2350 // only insert CanvasNotes into the map
2351 if (note) {
2352 NoteResizeData *resize_data = new NoteResizeData();
2353 resize_data->canvas_note = note;
2355 // create a new SimpleRect from the note which will be the resize preview
2356 SimpleRect *resize_rect = new SimpleRect(
2357 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2359 // calculate the colors: get the color settings
2360 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2361 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2362 128);
2364 // make the resize preview notes more transparent and bright
2365 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2367 // calculate color based on note velocity
2368 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2369 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2370 fill_color,
2371 0.85);
2373 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2374 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2376 resize_data->resize_rect = resize_rect;
2377 _resize_data.push_back(resize_data);
2382 /** Update resizing notes while user drags.
2383 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2384 * @param at_front which end of the note (true == note on, false == note off)
2385 * @param delta_x change in mouse position since the start of the drag
2386 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2387 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2388 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2389 * as the \a primary note.
2391 void
2392 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2394 bool cursor_set = false;
2396 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2397 SimpleRect* resize_rect = (*i)->resize_rect;
2398 CanvasNote* canvas_note = (*i)->canvas_note;
2399 double current_x;
2401 if (at_front) {
2402 if (relative) {
2403 current_x = canvas_note->x1() + delta_x;
2404 } else {
2405 current_x = primary->x1() + delta_x;
2407 } else {
2408 if (relative) {
2409 current_x = canvas_note->x2() + delta_x;
2410 } else {
2411 current_x = primary->x2() + delta_x;
2415 if (at_front) {
2416 resize_rect->property_x1() = snap_to_pixel(current_x);
2417 resize_rect->property_x2() = canvas_note->x2();
2418 } else {
2419 resize_rect->property_x2() = snap_to_pixel(current_x);
2420 resize_rect->property_x1() = canvas_note->x1();
2423 if (!cursor_set) {
2424 double beats;
2426 beats = snap_pixel_to_frame (current_x);
2427 beats = frames_to_beats (beats);
2429 double len;
2431 if (at_front) {
2432 if (beats < canvas_note->note()->end_time()) {
2433 len = canvas_note->note()->time() - beats;
2434 len += canvas_note->note()->length();
2435 } else {
2436 len = 0;
2438 } else {
2439 if (beats >= canvas_note->note()->time()) {
2440 len = beats - canvas_note->note()->time();
2441 } else {
2442 len = 0;
2446 char buf[16];
2447 snprintf (buf, sizeof (buf), "%.3g beats", len);
2448 show_verbose_cursor (buf, 0, 0);
2450 cursor_set = true;
2457 /** Finish resizing notes when the user releases the mouse button.
2458 * Parameters the same as for \a update_resizing().
2460 void
2461 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2463 start_note_diff_command (_("resize notes"));
2465 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2466 CanvasNote* canvas_note = (*i)->canvas_note;
2467 SimpleRect* resize_rect = (*i)->resize_rect;
2469 /* Get the new x position for this resize, which is in pixels relative
2470 * to the region position.
2473 double current_x;
2475 if (at_front) {
2476 if (relative) {
2477 current_x = canvas_note->x1() + delta_x;
2478 } else {
2479 current_x = primary->x1() + delta_x;
2481 } else {
2482 if (relative) {
2483 current_x = canvas_note->x2() + delta_x;
2484 } else {
2485 current_x = primary->x2() + delta_x;
2489 /* Convert that to a frame within the region */
2490 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2492 /* and then to beats */
2493 current_x = frames_to_beats (current_x);
2495 if (at_front && current_x < canvas_note->note()->end_time()) {
2496 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2498 double len = canvas_note->note()->time() - current_x;
2499 len += canvas_note->note()->length();
2501 if (len > 0) {
2502 /* XXX convert to beats */
2503 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2507 if (!at_front) {
2508 double len = current_x - canvas_note->note()->time();
2510 if (len > 0) {
2511 /* XXX convert to beats */
2512 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2516 delete resize_rect;
2517 delete (*i);
2520 _resize_data.clear();
2521 apply_diff();
2524 void
2525 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2527 uint8_t new_velocity;
2529 if (relative) {
2530 new_velocity = event->note()->velocity() + velocity;
2531 clamp_to_0_127(new_velocity);
2532 } else {
2533 new_velocity = velocity;
2536 event->set_selected (event->selected()); // change color
2538 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2541 void
2542 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2544 uint8_t new_note;
2546 if (relative) {
2547 new_note = event->note()->note() + note;
2548 } else {
2549 new_note = note;
2552 clamp_to_0_127 (new_note);
2553 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2556 void
2557 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2559 bool change_start = false;
2560 bool change_length = false;
2561 Evoral::MusicalTime new_start = 0;
2562 Evoral::MusicalTime new_length = 0;
2564 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2566 front_delta: if positive - move the start of the note later in time (shortening it)
2567 if negative - move the start of the note earlier in time (lengthening it)
2569 end_delta: if positive - move the end of the note later in time (lengthening it)
2570 if negative - move the end of the note earlier in time (shortening it)
2573 if (front_delta) {
2574 if (front_delta < 0) {
2576 if (event->note()->time() < -front_delta) {
2577 new_start = 0;
2578 } else {
2579 new_start = event->note()->time() + front_delta; // moves earlier
2582 /* start moved toward zero, so move the end point out to where it used to be.
2583 Note that front_delta is negative, so this increases the length.
2586 new_length = event->note()->length() - front_delta;
2587 change_start = true;
2588 change_length = true;
2590 } else {
2592 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2594 if (new_pos < event->note()->end_time()) {
2595 new_start = event->note()->time() + front_delta;
2596 /* start moved toward the end, so move the end point back to where it used to be */
2597 new_length = event->note()->length() - front_delta;
2598 change_start = true;
2599 change_length = true;
2605 if (end_delta) {
2606 bool can_change = true;
2607 if (end_delta < 0) {
2608 if (event->note()->length() < -end_delta) {
2609 can_change = false;
2613 if (can_change) {
2614 new_length = event->note()->length() + end_delta;
2615 change_length = true;
2619 if (change_start) {
2620 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2623 if (change_length) {
2624 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2628 void
2629 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2631 uint8_t new_channel;
2633 if (relative) {
2634 if (chn < 0.0) {
2635 if (event->note()->channel() < -chn) {
2636 new_channel = 0;
2637 } else {
2638 new_channel = event->note()->channel() + chn;
2640 } else {
2641 new_channel = event->note()->channel() + chn;
2643 } else {
2644 new_channel = (uint8_t) chn;
2647 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2650 void
2651 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2653 Evoral::MusicalTime new_time;
2655 if (relative) {
2656 if (delta < 0.0) {
2657 if (event->note()->time() < -delta) {
2658 new_time = 0;
2659 } else {
2660 new_time = event->note()->time() + delta;
2662 } else {
2663 new_time = event->note()->time() + delta;
2665 } else {
2666 new_time = delta;
2669 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2672 void
2673 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2675 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2678 void
2679 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2681 int8_t delta;
2683 if (_selection.empty()) {
2684 return;
2687 if (fine) {
2688 delta = 1;
2689 } else {
2690 delta = 10;
2693 if (!up) {
2694 delta = -delta;
2697 if (!allow_smush) {
2698 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2699 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2700 return;
2705 start_note_diff_command (_("change velocities"));
2707 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2708 Selection::iterator next = i;
2709 ++next;
2710 change_note_velocity (*i, delta, true);
2711 i = next;
2714 apply_diff();
2716 if (!_selection.empty()) {
2717 char buf[24];
2718 snprintf (buf, sizeof (buf), "Vel %d",
2719 (int) (*_selection.begin())->note()->velocity());
2720 show_verbose_cursor (buf, 10, 10);
2725 void
2726 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2728 if (_selection.empty()) {
2729 return;
2732 int8_t delta;
2734 if (fine) {
2735 delta = 1;
2736 } else {
2737 delta = 12;
2740 if (!up) {
2741 delta = -delta;
2744 if (!allow_smush) {
2745 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2746 if (!up) {
2747 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2748 return;
2750 } else {
2751 if ((int8_t) (*i)->note()->note() + delta > 127) {
2752 return;
2758 start_note_diff_command (_("transpose"));
2760 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2761 Selection::iterator next = i;
2762 ++next;
2763 change_note_note (*i, delta, true);
2764 i = next;
2767 apply_diff ();
2770 void
2771 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2773 if (delta == 0.0) {
2774 if (fine) {
2775 delta = 1.0/128.0;
2776 } else {
2777 /* grab the current grid distance */
2778 bool success;
2779 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2780 if (!success) {
2781 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2782 cerr << "Grid type not available as beats - TO BE FIXED\n";
2783 return;
2788 if (shorter) {
2789 delta = -delta;
2792 start_note_diff_command (_("change note lengths"));
2794 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2795 Selection::iterator next = i;
2796 ++next;
2798 /* note the negation of the delta for start */
2800 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2801 i = next;
2804 apply_diff ();
2808 void
2809 MidiRegionView::nudge_notes (bool forward)
2811 if (_selection.empty()) {
2812 return;
2815 /* pick a note as the point along the timeline to get the nudge distance.
2816 its not necessarily the earliest note, so we may want to pull the notes out
2817 into a vector and sort before using the first one.
2820 framepos_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2821 framepos_t unused;
2822 framepos_t distance;
2824 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2826 /* grid is off - use nudge distance */
2828 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2830 } else {
2832 /* use grid */
2834 framepos_t next_pos = ref_point;
2836 if (forward) {
2837 if (max_framepos - 1 < next_pos) {
2838 next_pos += 1;
2840 } else {
2841 if (next_pos == 0) {
2842 return;
2844 next_pos -= 1;
2847 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2848 distance = ref_point - next_pos;
2851 if (distance == 0) {
2852 return;
2855 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2857 if (!forward) {
2858 delta = -delta;
2861 start_note_diff_command (_("nudge"));
2863 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2864 Selection::iterator next = i;
2865 ++next;
2866 change_note_time (*i, delta, true);
2867 i = next;
2870 apply_diff ();
2873 void
2874 MidiRegionView::change_channel(uint8_t channel)
2876 start_note_diff_command(_("change channel"));
2877 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2878 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2881 apply_diff();
2885 void
2886 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2888 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2890 _pre_enter_cursor = editor->get_canvas_cursor ();
2892 if (_mouse_state == SelectTouchDragging) {
2893 note_selected (ev, true);
2896 show_verbose_cursor (ev->note ());
2899 void
2900 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2902 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2904 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2905 (*i)->hide_velocity ();
2908 editor->verbose_cursor()->hide ();
2910 if (_pre_enter_cursor) {
2911 editor->set_canvas_cursor (_pre_enter_cursor);
2912 _pre_enter_cursor = 0;
2916 void
2917 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2919 ostringstream s;
2920 s << ((int) ev->patch()->program() + 1) << ":" << (ev->patch()->bank() + 1);
2921 show_verbose_cursor (s.str(), 10, 20);
2924 void
2925 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
2927 trackview.editor().verbose_cursor()->hide ();
2930 void
2931 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
2933 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2935 if (x_fraction > 0.0 && x_fraction < 0.25) {
2936 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
2937 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
2938 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
2939 } else {
2940 if (_pre_enter_cursor && can_set_cursor) {
2941 editor->set_canvas_cursor (_pre_enter_cursor);
2946 void
2947 MidiRegionView::set_frame_color()
2949 uint32_t f;
2951 TimeAxisViewItem::set_frame_color ();
2953 if (!frame) {
2954 return;
2957 if (_selected) {
2958 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2959 } else if (high_enough_for_name) {
2960 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2961 } else {
2962 f = fill_color;
2965 if (!rect_visible) {
2966 f = UINT_RGBA_CHANGE_A (f, 0);
2969 frame->property_fill_color_rgba() = f;
2972 void
2973 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2975 switch (mode) {
2976 case AllChannels:
2977 case FilterChannels:
2978 _force_channel = -1;
2979 break;
2980 case ForceChannel:
2981 _force_channel = mask;
2982 mask = 0xFFFF; // Show all notes as active (below)
2985 // Update notes for selection
2986 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2987 (*i)->on_channel_selection_change(mask);
2990 _last_channel_selection = mask;
2992 _patch_changes.clear ();
2993 display_patch_changes ();
2996 void
2997 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2999 _model_name = model;
3000 _custom_device_mode = custom_device_mode;
3001 redisplay_model();
3004 void
3005 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3007 if (_selection.empty()) {
3008 return;
3011 PublicEditor& editor (trackview.editor());
3013 switch (op) {
3014 case Delete:
3015 /* XXX what to do ? */
3016 break;
3017 case Cut:
3018 case Copy:
3019 editor.get_cut_buffer().add (selection_as_cut_buffer());
3020 break;
3021 default:
3022 break;
3025 if (op != Copy) {
3027 start_note_diff_command();
3029 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3030 switch (op) {
3031 case Copy:
3032 break;
3033 case Delete:
3034 case Cut:
3035 case Clear:
3036 note_diff_remove_note (*i);
3037 break;
3041 apply_diff();
3045 MidiCutBuffer*
3046 MidiRegionView::selection_as_cut_buffer () const
3048 Notes notes;
3050 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3051 NoteType* n = (*i)->note().get();
3052 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3055 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3056 cb->set (notes);
3058 return cb;
3061 /** This method handles undo */
3062 void
3063 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3065 if (mcb.empty()) {
3066 return;
3069 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3071 trackview.session()->begin_reversible_command (_("paste"));
3073 start_note_diff_command (_("paste"));
3075 Evoral::MusicalTime beat_delta;
3076 Evoral::MusicalTime paste_pos_beats;
3077 Evoral::MusicalTime duration;
3078 Evoral::MusicalTime end_point = 0;
3080 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3081 paste_pos_beats = frames_to_beats (pos - _region->position());
3082 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3083 paste_pos_beats = 0;
3085 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3086 (*mcb.notes().begin())->time(),
3087 (*mcb.notes().rbegin())->end_time(),
3088 duration, pos, _region->position(),
3089 paste_pos_beats, beat_delta));
3091 clear_selection ();
3093 for (int n = 0; n < (int) times; ++n) {
3095 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3097 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3098 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3100 /* make all newly added notes selected */
3102 note_diff_add_note (copied_note, true);
3103 end_point = copied_note->end_time();
3106 paste_pos_beats += duration;
3109 /* if we pasted past the current end of the region, extend the region */
3111 framepos_t end_frame = _region->position() + beats_to_frames (end_point);
3112 framepos_t region_end = _region->position() + _region->length() - 1;
3114 if (end_frame > region_end) {
3116 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3118 _region->clear_changes ();
3119 _region->set_length (end_frame);
3120 trackview.session()->add_command (new StatefulDiffCommand (_region));
3123 apply_diff (true);
3125 trackview.session()->commit_reversible_command ();
3128 struct EventNoteTimeEarlyFirstComparator {
3129 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3130 return a->note()->time() < b->note()->time();
3134 void
3135 MidiRegionView::time_sort_events ()
3137 if (!_sort_needed) {
3138 return;
3141 EventNoteTimeEarlyFirstComparator cmp;
3142 _events.sort (cmp);
3144 _sort_needed = false;
3147 void
3148 MidiRegionView::goto_next_note (bool add_to_selection)
3150 bool use_next = false;
3152 if (_events.back()->selected()) {
3153 return;
3156 time_sort_events ();
3158 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3159 if ((*i)->selected()) {
3160 use_next = true;
3161 continue;
3162 } else if (use_next) {
3163 if (!add_to_selection) {
3164 unique_select (*i);
3165 } else {
3166 note_selected (*i, true, false);
3168 return;
3172 /* use the first one */
3174 unique_select (_events.front());
3178 void
3179 MidiRegionView::goto_previous_note (bool add_to_selection)
3181 bool use_next = false;
3183 if (_events.front()->selected()) {
3184 return;
3187 time_sort_events ();
3189 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3190 if ((*i)->selected()) {
3191 use_next = true;
3192 continue;
3193 } else if (use_next) {
3194 if (!add_to_selection) {
3195 unique_select (*i);
3196 } else {
3197 note_selected (*i, true, false);
3199 return;
3203 /* use the last one */
3205 unique_select (*(_events.rbegin()));
3208 void
3209 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3211 bool had_selected = false;
3213 time_sort_events ();
3215 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3216 if ((*i)->selected()) {
3217 selected.insert ((*i)->note());
3218 had_selected = true;
3222 if (allow_all_if_none_selected && !had_selected) {
3223 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3224 selected.insert ((*i)->note());
3229 void
3230 MidiRegionView::update_ghost_note (double x, double y)
3232 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3234 _last_ghost_x = x;
3235 _last_ghost_y = y;
3237 _note_group->w2i (x, y);
3238 framepos_t const f = snap_pixel_to_frame (x);
3240 bool success;
3241 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
3243 if (!success) {
3244 beats = 1;
3247 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
3249 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
3250 _ghost_note->note()->set_length (length);
3251 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3252 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3254 /* the ghost note does not appear in ghost regions, so pass false in here */
3255 update_note (_ghost_note, false);
3257 show_verbose_cursor (_ghost_note->note ());
3260 void
3261 MidiRegionView::create_ghost_note (double x, double y)
3263 delete _ghost_note;
3264 _ghost_note = 0;
3266 boost::shared_ptr<NoteType> g (new NoteType);
3267 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3268 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3269 update_ghost_note (x, y);
3270 _ghost_note->show ();
3272 _last_ghost_x = x;
3273 _last_ghost_y = y;
3275 show_verbose_cursor (_ghost_note->note ());
3278 void
3279 MidiRegionView::snap_changed ()
3281 if (!_ghost_note) {
3282 return;
3285 create_ghost_note (_last_ghost_x, _last_ghost_y);
3288 void
3289 MidiRegionView::drop_down_keys ()
3291 _mouse_state = None;
3294 void
3295 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3297 double note = midi_stream_view()->y_to_note(y);
3298 Events e;
3299 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3301 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3303 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3304 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3305 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3306 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3307 } else {
3308 return;
3311 bool add_mrv_selection = false;
3313 if (_selection.empty()) {
3314 add_mrv_selection = true;
3317 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3318 if (_selection.insert (*i).second) {
3319 (*i)->set_selected (true);
3323 if (add_mrv_selection) {
3324 PublicEditor& editor (trackview.editor());
3325 editor.get_selection().add (this);
3329 void
3330 MidiRegionView::color_handler ()
3332 RegionView::color_handler ();
3334 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3335 (*i)->set_selected ((*i)->selected()); // will change color
3338 /* XXX probably more to do here */
3341 void
3342 MidiRegionView::enable_display (bool yn)
3344 RegionView::enable_display (yn);
3345 if (yn) {
3346 redisplay_model ();
3350 void
3351 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3353 if (_step_edit_cursor == 0) {
3354 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3356 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3357 _step_edit_cursor->property_y1() = 0;
3358 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3359 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3360 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3363 move_step_edit_cursor (pos);
3364 _step_edit_cursor->show ();
3367 void
3368 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3370 _step_edit_cursor_position = pos;
3372 if (_step_edit_cursor) {
3373 double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos));
3374 _step_edit_cursor->property_x1() = pixel;
3375 set_step_edit_cursor_width (_step_edit_cursor_width);
3379 void
3380 MidiRegionView::hide_step_edit_cursor ()
3382 if (_step_edit_cursor) {
3383 _step_edit_cursor->hide ();
3387 void
3388 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3390 _step_edit_cursor_width = beats;
3392 if (_step_edit_cursor) {
3393 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (beats_to_frames (beats));
3397 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3398 * @param buf Data that has been recorded.
3399 * @param w Source that this data will end up in.
3401 void
3402 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3404 if (!_active_notes) {
3405 /* we aren't actively being recorded to */
3406 return;
3409 boost::shared_ptr<MidiSource> src = w.lock ();
3410 if (!src || src != midi_region()->midi_source()) {
3411 /* recorded data was not destined for our source */
3412 return;
3415 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3416 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3418 framepos_t back = max_framepos;
3420 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3421 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3422 assert (ev.buffer ());
3424 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3426 if (ev.type() == MIDI_CMD_NOTE_ON) {
3428 boost::shared_ptr<NoteType> note (
3429 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3432 add_note (note, true);
3434 /* fix up our note range */
3435 if (ev.note() < _current_range_min) {
3436 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3437 } else if (ev.note() > _current_range_max) {
3438 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3441 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3442 resolve_note (ev.note (), time_beats);
3445 back = ev.time ();
3448 midi_stream_view()->check_record_layers (region(), back);
3451 void
3452 MidiRegionView::trim_front_starting ()
3454 /* Reparent the note group to the region view's parent, so that it doesn't change
3455 when the region view is trimmed.
3457 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3458 _temporary_note_group->move (group->property_x(), group->property_y());
3459 _note_group->reparent (*_temporary_note_group);
3462 void
3463 MidiRegionView::trim_front_ending ()
3465 _note_group->reparent (*group);
3466 delete _temporary_note_group;
3467 _temporary_note_group = 0;
3469 if (_region->start() < 0) {
3470 /* Trim drag made start time -ve; fix this */
3471 midi_region()->fix_negative_start ();
3475 void
3476 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3478 PatchChangeDialog d (&_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3479 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3480 return;
3483 change_patch_change (pc->patch(), d.patch ());
3487 void
3488 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3490 char buf[24];
3491 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3492 Evoral::midi_note_name (n->note()).c_str(),
3493 (int) n->note (),
3494 (int) n->channel() + 1,
3495 (int) n->velocity());
3497 show_verbose_cursor (buf, 10, 20);
3500 void
3501 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3503 double wx, wy;
3505 trackview.editor().get_pointer_position (wx, wy);
3507 wx += xoffset;
3508 wy += yoffset;
3510 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3512 double x1, y1, x2, y2;
3513 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3515 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3516 wy -= (y2 - y1) + 2 * yoffset;
3519 trackview.editor().verbose_cursor()->set (text, wx, wy);
3520 trackview.editor().verbose_cursor()->show ();