1 /*
2  * Copyright (C) 2006-2016 David Robillard <d@drobilla.net>
3  * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
4  * Copyright (C) 2007-2018 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2008-2012 Hans Baier <hansfbaier@googlemail.com>
6  * Copyright (C) 2013-2017 John Emmas <john@creativepost.co.uk>
7  * Copyright (C) 2014-2017 Nick Mainsbridge <mainsbridge@gmail.com>
8  * Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
9  * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
10  * Copyright (C) 2015-2016 Tim Mayberry <mojofunk@gmail.com>
11  * Copyright (C) 2015-2017 André Nusser <andre.nusser@googlemail.com>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License along
24  * with this program; if not, write to the Free Software Foundation, Inc.,
25  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26  */
27 
28 #include <cmath>
29 #include <algorithm>
30 #include <ostream>
31 
32 #include <gtkmm.h>
33 
34 #include "gtkmm2ext/gtk_ui.h"
35 
36 #include <sigc++/signal.h>
37 
38 #include "midi++/midnam_patch.h"
39 
40 #include "pbd/memento_command.h"
41 #include "pbd/stateful_diff_command.h"
42 #include "pbd/unwind.h"
43 
44 #include "ardour/debug.h"
45 #include "ardour/midi_model.h"
46 #include "ardour/midi_playlist.h"
47 #include "ardour/midi_region.h"
48 #include "ardour/midi_source.h"
49 #include "ardour/midi_track.h"
50 #include "ardour/operations.h"
51 #include "ardour/session.h"
52 
53 #include "evoral/Parameter.h"
54 #include "evoral/Event.h"
55 #include "evoral/Control.h"
56 #include "evoral/midi_util.h"
57 
58 #include "canvas/debug.h"
59 #include "canvas/text.h"
60 
61 #include "automation_region_view.h"
62 #include "automation_time_axis.h"
63 #include "control_point.h"
64 #include "debug.h"
65 #include "editor.h"
66 #include "editor_drag.h"
67 #include "ghostregion.h"
68 #include "gui_thread.h"
69 #include "item_counts.h"
70 #include "keyboard.h"
71 #include "midi_channel_dialog.h"
72 #include "midi_cut_buffer.h"
73 #include "midi_list_editor.h"
74 #include "midi_region_view.h"
75 #include "midi_streamview.h"
76 #include "midi_time_axis.h"
77 #include "midi_util.h"
78 #include "midi_velocity_dialog.h"
79 #include "mouse_cursors.h"
80 #include "note_player.h"
81 #include "paste_context.h"
82 #include "public_editor.h"
83 #include "route_time_axis.h"
84 #include "rgb_macros.h"
85 #include "selection.h"
86 #include "streamview.h"
87 #include "patch_change_dialog.h"
88 #include "verbose_cursor.h"
89 #include "note.h"
90 #include "hit.h"
91 #include "patch_change.h"
92 #include "sys_ex.h"
93 #include "ui_config.h"
94 
95 #include "pbd/i18n.h"
96 
97 using namespace ARDOUR;
98 using namespace PBD;
99 using namespace Editing;
100 using namespace std;
101 using Gtkmm2ext::Keyboard;
102 
103 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
104 
MidiRegionView(ArdourCanvas::Container * parent,RouteTimeAxisView & tv,boost::shared_ptr<MidiRegion> r,double spu,uint32_t basic_color)105 MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
106                                 RouteTimeAxisView&            tv,
107                                 boost::shared_ptr<MidiRegion> r,
108                                 double                        spu,
109                                 uint32_t                      basic_color)
110 	: RegionView (parent, tv, r, spu, basic_color)
111 	, _current_range_min(0)
112 	, _current_range_max(0)
113 	, _region_relative_time_converter(r->session().tempo_map(), r->position())
114 	, _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
115 	, _region_relative_time_converter_double(r->session().tempo_map(), r->position())
116 	, _active_notes(0)
117 	, _note_group (new ArdourCanvas::Container (group))
118 	, _note_diff_command (0)
119 	, _ghost_note(0)
120 	, _step_edit_cursor (0)
121 	, _step_edit_cursor_width (1.0)
122 	, _step_edit_cursor_position (0.0)
123 	, _channel_selection_scoped_note (0)
124 	, _mouse_state(None)
125 	, _pressed_button(0)
126 	, _optimization_iterator (_events.end())
127 	, _list_editor (0)
128 	, _no_sound_notes (false)
129 	, _last_display_zoom (0)
130 	, _last_event_x (0)
131 	, _last_event_y (0)
132 	, _entered (false)
133 	, _entered_note (0)
134 	, _mouse_changed_selection (false)
135 {
136 	CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
137 
138 	_patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
139 	_patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
140 
141 	_note_group->raise_to_top();
142 	PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
143 
144 	Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
145 
146 	connect_to_diskstream ();
147 }
148 
MidiRegionView(ArdourCanvas::Container * parent,RouteTimeAxisView & tv,boost::shared_ptr<MidiRegion> r,double spu,uint32_t basic_color,bool recording,TimeAxisViewItem::Visibility visibility)149 MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
150                                 RouteTimeAxisView&            tv,
151                                 boost::shared_ptr<MidiRegion> r,
152                                 double                        spu,
153                                 uint32_t                      basic_color,
154                                 bool                          recording,
155                                 TimeAxisViewItem::Visibility  visibility)
156 	: RegionView (parent, tv, r, spu, basic_color, recording, visibility)
157 	, _current_range_min(0)
158 	, _current_range_max(0)
159 	, _region_relative_time_converter(r->session().tempo_map(), r->position())
160 	, _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
161 	, _region_relative_time_converter_double(r->session().tempo_map(), r->position())
162 	, _active_notes(0)
163 	, _note_group (new ArdourCanvas::Container (group))
164 	, _note_diff_command (0)
165 	, _ghost_note(0)
166 	, _step_edit_cursor (0)
167 	, _step_edit_cursor_width (1.0)
168 	, _step_edit_cursor_position (0.0)
169 	, _channel_selection_scoped_note (0)
170 	, _mouse_state(None)
171 	, _pressed_button(0)
172 	, _optimization_iterator (_events.end())
173 	, _list_editor (0)
174 	, _no_sound_notes (false)
175 	, _last_display_zoom (0)
176 	, _last_event_x (0)
177 	, _last_event_y (0)
178 	, _entered (false)
179 	, _entered_note (0)
180 	, _mouse_changed_selection (false)
181 {
182 	CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
183 
184 	_patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
185 	_patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
186 
187 	_note_group->raise_to_top();
188 
189 	PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
190 
191 	connect_to_diskstream ();
192 }
193 
194 void
parameter_changed(std::string const & p)195 MidiRegionView::parameter_changed (std::string const & p)
196 {
197 	RegionView::parameter_changed (p);
198 	if (p == "display-first-midi-bank-as-zero") {
199 		if (_enable_display) {
200 			redisplay_model();
201 		}
202 	} else if (p == "color-regions-using-track-color") {
203 		set_colors ();
204 	} else if (p == "use-note-color-for-velocity") {
205 		color_handler ();
206 	}
207 }
208 
MidiRegionView(const MidiRegionView & other)209 MidiRegionView::MidiRegionView (const MidiRegionView& other)
210 	: sigc::trackable(other)
211 	, RegionView (other)
212 	, _current_range_min(0)
213 	, _current_range_max(0)
214 	, _region_relative_time_converter(other.region_relative_time_converter())
215 	, _source_relative_time_converter(other.source_relative_time_converter())
216 	, _region_relative_time_converter_double(other.region_relative_time_converter_double())
217 	, _active_notes(0)
218 	, _note_group (new ArdourCanvas::Container (get_canvas_group()))
219 	, _note_diff_command (0)
220 	, _ghost_note(0)
221 	, _step_edit_cursor (0)
222 	, _step_edit_cursor_width (1.0)
223 	, _step_edit_cursor_position (0.0)
224 	, _channel_selection_scoped_note (0)
225 	, _mouse_state(None)
226 	, _pressed_button(0)
227 	, _optimization_iterator (_events.end())
228 	, _list_editor (0)
229 	, _no_sound_notes (false)
230 	, _last_display_zoom (0)
231 	, _last_event_x (0)
232 	, _last_event_y (0)
233 	, _entered (false)
234 	, _entered_note (0)
235 	, _mouse_changed_selection (false)
236 {
237 	init (false);
238 }
239 
MidiRegionView(const MidiRegionView & other,boost::shared_ptr<MidiRegion> region)240 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
241 	: RegionView (other, boost::shared_ptr<Region> (region))
242 	, _current_range_min(0)
243 	, _current_range_max(0)
244 	, _region_relative_time_converter(other.region_relative_time_converter())
245 	, _source_relative_time_converter(other.source_relative_time_converter())
246 	, _region_relative_time_converter_double(other.region_relative_time_converter_double())
247 	, _active_notes(0)
248 	, _note_group (new ArdourCanvas::Container (get_canvas_group()))
249 	, _note_diff_command (0)
250 	, _ghost_note(0)
251 	, _step_edit_cursor (0)
252 	, _step_edit_cursor_width (1.0)
253 	, _step_edit_cursor_position (0.0)
254 	, _channel_selection_scoped_note (0)
255 	, _mouse_state(None)
256 	, _pressed_button(0)
257 	, _optimization_iterator (_events.end())
258 	, _list_editor (0)
259 	, _no_sound_notes (false)
260 	, _last_display_zoom (0)
261 	, _last_event_x (0)
262 	, _last_event_y (0)
263 	, _entered (false)
264 	, _entered_note (0)
265 	, _mouse_changed_selection (false)
266 {
267 	init (true);
268 }
269 
270 void
init(bool wfd)271 MidiRegionView::init (bool wfd)
272 {
273 	PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
274 
275 	if (wfd) {
276 		Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
277 		midi_region()->midi_source(0)->load_model(lm);
278 	}
279 
280 	_model = midi_region()->midi_source(0)->model();
281 	_enable_display = false;
282 	fill_color_name = "midi frame base";
283 
284 	RegionView::init (false);
285 
286 	//set_height (trackview.current_height());
287 
288 	region_muted ();
289 	region_sync_changed ();
290 	region_resized (ARDOUR::bounds_change);
291 	//region_locked ();
292 
293 	set_colors ();
294 
295 	_enable_display = true;
296 	if (_model) {
297 		if (wfd) {
298 			display_model (_model);
299 		}
300 	}
301 
302 	reset_width_dependent_items (_pixel_width);
303 
304 	group->raise_to_top();
305 
306 	midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
307 								       boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
308 								       gui_context ());
309 
310 	instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
311 					   boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
312 
313 	trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
314 	                                       boost::bind (&MidiRegionView::snap_changed, this),
315 	                                       gui_context());
316 
317 	trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
318 	                                            boost::bind (&MidiRegionView::mouse_mode_changed, this),
319 	                                            gui_context ());
320 
321 	Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
322 	connect_to_diskstream ();
323 }
324 
325 InstrumentInfo&
instrument_info() const326 MidiRegionView::instrument_info () const
327 {
328 	RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
329 	return route_ui->route()->instrument_info();
330 }
331 
332 const boost::shared_ptr<ARDOUR::MidiRegion>
midi_region() const333 MidiRegionView::midi_region() const
334 {
335 	return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
336 }
337 
338 void
connect_to_diskstream()339 MidiRegionView::connect_to_diskstream ()
340 {
341 	midi_view()->midi_track()->DataRecorded.connect(
342 		*this, invalidator(*this),
343 		boost::bind (&MidiRegionView::data_recorded, this, _1),
344 		gui_context());
345 }
346 
347 bool
canvas_group_event(GdkEvent * ev)348 MidiRegionView::canvas_group_event(GdkEvent* ev)
349 {
350 	if (in_destructor || _recregion) {
351 		return false;
352 	}
353 
354 	if (!trackview.editor().internal_editing()) {
355 		// not in internal edit mode, so just act like a normal region
356 		return RegionView::canvas_group_event (ev);
357 	}
358 
359 	//For now, move the snapped cursor aside so it doesn't bother you during internal editing
360 	//trackview.editor().set_snapped_cursor_position(_region->position());
361 
362 	bool r;
363 
364 	switch (ev->type) {
365 	case GDK_ENTER_NOTIFY:
366 		_last_event_x = ev->crossing.x;
367 		_last_event_y = ev->crossing.y;
368 		enter_notify(&ev->crossing);
369 		// set entered_regionview (among other things)
370 		return RegionView::canvas_group_event (ev);
371 
372 	case GDK_LEAVE_NOTIFY:
373 		_last_event_x = ev->crossing.x;
374 		_last_event_y = ev->crossing.y;
375 		leave_notify(&ev->crossing);
376 		// reset entered_regionview (among other things)
377 		return RegionView::canvas_group_event (ev);
378 
379 	case GDK_SCROLL:
380 		if (scroll (&ev->scroll)) {
381 			return true;
382 		}
383 		break;
384 
385 	case GDK_KEY_PRESS:
386 		return key_press (&ev->key);
387 
388 	case GDK_KEY_RELEASE:
389 		return key_release (&ev->key);
390 
391 	case GDK_BUTTON_PRESS:
392 		return button_press (&ev->button);
393 
394 	case GDK_BUTTON_RELEASE:
395 		r = button_release (&ev->button);
396 		return r;
397 
398 	case GDK_MOTION_NOTIFY:
399 		_last_event_x = ev->motion.x;
400 		_last_event_y = ev->motion.y;
401 		return motion (&ev->motion);
402 
403 	default:
404 		break;
405 	}
406 
407 	return RegionView::canvas_group_event (ev);
408 }
409 
410 bool
enter_notify(GdkEventCrossing * ev)411 MidiRegionView::enter_notify (GdkEventCrossing* ev)
412 {
413 	enter_internal (ev->state);
414 
415 	_entered = true;
416 	return false;
417 }
418 
419 bool
leave_notify(GdkEventCrossing *)420 MidiRegionView::leave_notify (GdkEventCrossing*)
421 {
422 	leave_internal ();
423 
424 	_entered = false;
425 	return false;
426 }
427 
428 void
mouse_mode_changed()429 MidiRegionView::mouse_mode_changed ()
430 {
431 	// Adjust frame colour (become more transparent for internal tools)
432 	set_frame_color();
433 
434 	if (!trackview.editor().internal_editing()) {
435 
436 		/* Switched out of internal editing mode while entered.
437 		   Only necessary for leave as a mouse_mode_change over a region
438 		   automatically triggers an enter event.
439 		*/
440 
441 		leave_internal ();
442 
443 		for (Events::iterator it = _events.begin(); it != _events.end(); ++it) {
444 			it->second->set_hide_selection (true);
445 		}
446 
447 	} else if (trackview.editor().current_mouse_mode() == MouseContent) {
448 
449 		// hide cursor and ghost note after changing to internal edit mode
450 
451 		remove_ghost_note ();
452 
453 		/* XXX This is problematic as the function is executed for every region
454 		   and only for one region _entered_note can be true. Still it's
455 		   necessary as to hide the verbose cursor when we're changing from
456 		   draw mode to internal edit mode. These lines are the reason why
457 		   in some situations no verbose cursor is shown when we enter internal
458 		   edit mode over a note.
459 		*/
460 
461 		if (!_entered_note) {
462 			hide_verbose_cursor ();
463 		}
464 
465 		for (Events::iterator it = _events.begin(); it != _events.end(); ++it) {
466 			it->second->set_hide_selection (false);
467 		}
468 	}
469 }
470 
471 void
enter_internal(uint32_t state)472 MidiRegionView::enter_internal (uint32_t state)
473 {
474 	if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
475 		// Show ghost note under pencil
476 		create_ghost_note(_last_event_x, _last_event_y, state);
477 	}
478 
479 	// Lower frame handles below notes so they don't steal events
480 	if (frame_handle_start) {
481 		frame_handle_start->lower_to_bottom();
482 	}
483 	if (frame_handle_end) {
484 		frame_handle_end->lower_to_bottom();
485 	}
486 }
487 
488 void
leave_internal()489 MidiRegionView::leave_internal()
490 {
491 	hide_verbose_cursor ();
492 	remove_ghost_note ();
493 	_entered_note = 0;
494 
495 	// Raise frame handles above notes so they catch events
496 	if (frame_handle_start) {
497 		frame_handle_start->raise_to_top();
498 	}
499 	if (frame_handle_end) {
500 		frame_handle_end->raise_to_top();
501 	}
502 }
503 
504 bool
button_press(GdkEventButton * ev)505 MidiRegionView::button_press (GdkEventButton* ev)
506 {
507 	if (ev->button != 1) {
508 		return false;
509 	}
510 
511 	Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
512 	MouseMode m = editor->current_mouse_mode();
513 
514 	if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
515 		_press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
516 	}
517 
518 	if (_mouse_state != SelectTouchDragging) {
519 
520 		_pressed_button = ev->button;
521 
522 		if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
523 
524 			if (midi_view()->note_mode() == Percussive) {
525 				editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
526 			} else {
527 				editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
528 			}
529 
530 			_mouse_state = AddDragging;
531 			remove_ghost_note ();
532 			hide_verbose_cursor ();
533 		} else {
534 			_mouse_state = Pressed;
535 		}
536 
537 		return true;
538 	}
539 
540 	_pressed_button = ev->button;
541 	_mouse_changed_selection = false;
542 
543 	return true;
544 }
545 
546 bool
button_release(GdkEventButton * ev)547 MidiRegionView::button_release (GdkEventButton* ev)
548 {
549 	double event_x, event_y;
550 
551 	if (ev->button != 1) {
552 		return false;
553 	}
554 
555 	event_x = ev->x;
556 	event_y = ev->y;
557 
558 	group->canvas_to_item (event_x, event_y);
559 	group->ungrab ();
560 
561 	PublicEditor& editor = trackview.editor ();
562 
563 	_press_cursor_ctx.reset();
564 
565 	switch (_mouse_state) {
566 	case Pressed: // Clicked
567 
568 		switch (editor.current_mouse_mode()) {
569 		case MouseRange:
570 			/* no motion occurred - simple click */
571 			clear_selection_internal ();
572 			_mouse_changed_selection = true;
573 			break;
574 
575 		case MouseContent:
576 		case MouseTimeFX:
577 			_mouse_changed_selection = true;
578 			clear_selection_internal ();
579 			break;
580 		case MouseDraw:
581 			break;
582 
583 		default:
584 			break;
585 		}
586 
587 		_mouse_state = None;
588 		break;
589 
590 	case AddDragging:
591 		/* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
592 		   we don't want one when we were drag-selecting either. */
593 	case SelectRectDragging:
594 		editor.drags()->end_grab ((GdkEvent *) ev);
595 		_mouse_state = None;
596 		break;
597 
598 
599 	default:
600 		break;
601 	}
602 
603 	if (_mouse_changed_selection) {
604 		trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
605 		trackview.editor().commit_reversible_selection_op ();
606 	}
607 
608 	return false;
609 }
610 
611 bool
motion(GdkEventMotion * ev)612 MidiRegionView::motion (GdkEventMotion* ev)
613 {
614 	PublicEditor& editor = trackview.editor ();
615 
616 	if (!_entered_note) {
617 
618 		if (_mouse_state == AddDragging) {
619 			if (_ghost_note) {
620 				remove_ghost_note ();
621 			}
622 
623 		} else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
624 		    Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
625 		    _mouse_state != AddDragging) {
626 
627 			create_ghost_note (ev->x, ev->y, ev->state);
628 
629 		} else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
630 			   Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
631 
632 			update_ghost_note (ev->x, ev->y, ev->state);
633 
634 		} else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
635 
636 			remove_ghost_note ();
637 			hide_verbose_cursor ();
638 
639 		} else if (editor.current_mouse_mode() == MouseDraw) {
640 
641 			if (_ghost_note) {
642 				update_ghost_note (ev->x, ev->y, ev->state);
643 			}
644 			else {
645 				create_ghost_note (ev->x, ev->y, ev->state);
646 			}
647 		}
648 	}
649 
650 	/* any motion immediately hides velocity text that may have been visible */
651 
652 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
653 		(*i)->hide_velocity ();
654 	}
655 
656 	switch (_mouse_state) {
657 	case Pressed:
658 
659 		if (_pressed_button == 1) {
660 
661 			MouseMode m = editor.current_mouse_mode();
662 
663 			if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
664 				editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
665 				if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
666 					clear_selection_internal ();
667 					_mouse_changed_selection = true;
668 				}
669 				_mouse_state = SelectRectDragging;
670 				return true;
671 			} else if (m == MouseRange) {
672 				editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
673 				_mouse_state = SelectVerticalDragging;
674 				return true;
675 			}
676 		}
677 
678 		return false;
679 
680 	case SelectRectDragging:
681 	case SelectVerticalDragging:
682 	case AddDragging:
683 		editor.drags()->motion_handler ((GdkEvent *) ev, false);
684 		break;
685 
686 	case SelectTouchDragging:
687 		return false;
688 
689 	default:
690 		break;
691 
692 	}
693 
694 	//let RegionView do it's thing.  drags are handled in here
695 	return RegionView::canvas_group_event ((GdkEvent *) ev);
696 }
697 
698 
699 bool
scroll(GdkEventScroll * ev)700 MidiRegionView::scroll (GdkEventScroll* ev)
701 {
702 	if (trackview.editor().drags()->active()) {
703 		return false;
704 	}
705 	if (_selection.empty()) {
706 		return false;
707 	}
708 
709 	if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
710 	    Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
711 		/* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
712 		 * through so that it still works for navigation.
713 		*/
714 		return false;
715 	}
716 
717 	hide_verbose_cursor ();
718 
719 	bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
720 	Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
721 	bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
722 
723 	if (ev->direction == GDK_SCROLL_UP) {
724 		change_velocities (true, fine, false, together);
725 	} else if (ev->direction == GDK_SCROLL_DOWN) {
726 		change_velocities (false, fine, false, together);
727 	} else {
728 		/* left, right: we don't use them */
729 		return false;
730 	}
731 
732 	return true;
733 }
734 
735 bool
key_press(GdkEventKey * ev)736 MidiRegionView::key_press (GdkEventKey* ev)
737 {
738 	/* since GTK bindings are generally activated on press, and since
739 	   detectable auto-repeat is the name of the game and only sends
740 	   repeated presses, carry out key actions at key press, not release.
741 	*/
742 
743 	if (Keyboard::no_modifier_keys_pressed(ev) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
744 
745 		if (_mouse_state != AddDragging) {
746 			_mouse_state = SelectTouchDragging;
747 		}
748 
749 		return true;
750 	}
751 
752 	return false;
753 }
754 
755 bool
key_release(GdkEventKey * ev)756 MidiRegionView::key_release (GdkEventKey* ev)
757 {
758 	if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
759 		_mouse_state = None;
760 		return true;
761 	}
762 	return false;
763 }
764 
765 void
channel_edit()766 MidiRegionView::channel_edit ()
767 {
768 	if (_selection.empty()) {
769 		return;
770 	}
771 
772 	/* pick a note somewhat at random (since Selection is a set<>) to
773 	 * provide the "current" channel for the dialog.
774 	 */
775 
776 	uint8_t current_channel = (*_selection.begin())->note()->channel ();
777 	MidiChannelDialog channel_dialog (current_channel);
778 	int ret = channel_dialog.run ();
779 
780 	switch (ret) {
781 	case Gtk::RESPONSE_OK:
782 		break;
783 	default:
784 		return;
785 	}
786 
787 	uint8_t new_channel = channel_dialog.active_channel ();
788 
789 	start_note_diff_command (_("channel edit"));
790 
791 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
792 		Selection::iterator next = i;
793 		++next;
794 		change_note_channel (*i, new_channel);
795 		i = next;
796 	}
797 
798 	apply_diff ();
799 }
800 
801 void
velocity_edit()802 MidiRegionView::velocity_edit ()
803 {
804 	if (_selection.empty()) {
805 		return;
806 	}
807 
808 	/* pick a note somewhat at random (since Selection is a set<>) to
809 	 * provide the "current" velocity for the dialog.
810 	 */
811 
812 	uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
813 	MidiVelocityDialog velocity_dialog (current_velocity);
814 	int ret = velocity_dialog.run ();
815 
816 	switch (ret) {
817 	case Gtk::RESPONSE_OK:
818 		break;
819 	default:
820 		return;
821 	}
822 
823 	uint8_t new_velocity = velocity_dialog.velocity ();
824 
825 	start_note_diff_command (_("velocity edit"));
826 
827 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
828 		Selection::iterator next = i;
829 		++next;
830 		change_note_velocity (*i, new_velocity, false);
831 		i = next;
832 	}
833 
834 	apply_diff ();
835 }
836 
837 void
show_list_editor()838 MidiRegionView::show_list_editor ()
839 {
840 	if (!_list_editor) {
841 		_list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
842 	}
843 	_list_editor->present ();
844 }
845 
846 /** Add a note to the model, and the view, at a canvas (click) coordinate.
847  * \param t time in samples relative to the position of the region
848  * \param y vertical position in pixels
849  * \param length duration of the note in beats
850  * \param snap_t true to snap t to the grid, otherwise false.
851  */
852 void
create_note_at(samplepos_t t,double y,Temporal::Beats length,uint32_t state,bool shift_snap)853 MidiRegionView::create_note_at (samplepos_t t, double y, Temporal::Beats length, uint32_t state, bool shift_snap)
854 {
855 	if (length < 2 * DBL_EPSILON) {
856 		return;
857 	}
858 
859 	MidiTimeAxisView* const mtv  = dynamic_cast<MidiTimeAxisView*>(&trackview);
860 	MidiStreamView* const   view = mtv->midi_view();
861 	boost::shared_ptr<MidiRegion> mr  = boost::dynamic_pointer_cast<MidiRegion> (_region);
862 
863 	if (!mr) {
864 		return;
865 	}
866 
867 	// Start of note in samples relative to region start
868 	const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
869 	Temporal::Beats beat_time = snap_sample_to_grid_underneath (t, divisions, shift_snap);
870 
871 	const double  note     = view->y_to_note(y);
872 	const uint8_t chan     = mtv->get_channel_for_add();
873 	const uint8_t velocity = get_velocity_for_add(beat_time);
874 
875 	const boost::shared_ptr<NoteType> new_note(
876 		new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
877 
878 	if (_model->contains (new_note)) {
879 		return;
880 	}
881 
882 	view->update_note_range(new_note->note());
883 
884 	start_note_diff_command(_("add note"));
885 
886 	note_diff_add_note (new_note, true, false);
887 
888 	apply_diff();
889 
890 	trackview.editor().set_selected_midi_region_view (*this);
891 	list<Evoral::event_id_t> to_be_selected;
892 	to_be_selected.push_back (new_note->id());
893 	select_notes (to_be_selected, true);
894 
895 	play_midi_note (new_note);
896 }
897 
898 void
clear_events()899 MidiRegionView::clear_events ()
900 {
901 	// clear selection without signaling or trying to change state of event objects
902 	_selection.clear ();
903 
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();
908 		}
909 	}
910 
911 
912 	_note_group->clear (true);
913 	_events.clear();
914 	_patch_changes.clear();
915 	_sys_exes.clear();
916 	_optimization_iterator = _events.end();
917 }
918 
919 void
display_model(boost::shared_ptr<MidiModel> model)920 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
921 {
922 	_model = model;
923 
924 	content_connection.disconnect ();
925 	_model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
926 	/* Don't signal as nobody else needs to know until selection has been altered. */
927 	clear_events();
928 
929 	if (_enable_display) {
930 		redisplay_model();
931 	}
932 }
933 
934 void
start_note_diff_command(string name)935 MidiRegionView::start_note_diff_command (string name)
936 {
937 	if (!_note_diff_command) {
938 		trackview.editor().begin_reversible_command (name);
939 		_note_diff_command = _model->new_note_diff_command (name);
940 	}
941 }
942 
943 void
note_diff_add_note(const boost::shared_ptr<NoteType> note,bool selected,bool show_velocity)944 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
945 {
946 	if (_note_diff_command) {
947 		_note_diff_command->add (note);
948 	}
949 	if (selected) {
950 		_marked_for_selection.insert(note);
951 	}
952 	if (show_velocity) {
953 		_marked_for_velocity.insert(note);
954 	}
955 }
956 
957 void
note_diff_remove_note(NoteBase * ev)958 MidiRegionView::note_diff_remove_note (NoteBase* ev)
959 {
960 	if (_note_diff_command && ev->note()) {
961 		_note_diff_command->remove(ev->note());
962 	}
963 }
964 
965 void
note_diff_add_change(NoteBase * ev,MidiModel::NoteDiffCommand::Property property,uint8_t val)966 MidiRegionView::note_diff_add_change (NoteBase* ev,
967                                       MidiModel::NoteDiffCommand::Property property,
968                                       uint8_t val)
969 {
970 	if (_note_diff_command) {
971 		_note_diff_command->change (ev->note(), property, val);
972 	}
973 }
974 
975 void
note_diff_add_change(NoteBase * ev,MidiModel::NoteDiffCommand::Property property,Temporal::Beats val)976 MidiRegionView::note_diff_add_change (NoteBase* ev,
977                                       MidiModel::NoteDiffCommand::Property property,
978                                       Temporal::Beats val)
979 {
980 	if (_note_diff_command) {
981 		_note_diff_command->change (ev->note(), property, val);
982 	}
983 }
984 
985 void
apply_diff(bool as_subcommand,bool was_copy)986 MidiRegionView::apply_diff (bool as_subcommand, bool was_copy)
987 {
988 	bool commit = false;
989 
990 	if (!_note_diff_command) {
991 		return;
992 	}
993 
994 	bool add_or_remove = _note_diff_command->adds_or_removes();
995 
996 	if (!was_copy && add_or_remove) {
997 		// Mark all selected notes for selection when model reloads
998 		for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
999 			_marked_for_selection.insert((*i)->note());
1000 		}
1001 	}
1002 
1003 	if (as_subcommand) {
1004 		_model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1005 	} else {
1006 		_model->apply_command (*trackview.session(), _note_diff_command);
1007 		commit = true;
1008 	}
1009 
1010 	_note_diff_command = 0;
1011 
1012 	if (add_or_remove) {
1013 		_marked_for_selection.clear();
1014 	}
1015 
1016 	_marked_for_velocity.clear();
1017 
1018 	if (commit) {
1019 		trackview.editor().commit_reversible_command ();
1020 	}
1021 }
1022 
1023 void
abort_command()1024 MidiRegionView::abort_command()
1025 {
1026 	delete _note_diff_command;
1027 	_note_diff_command = 0;
1028 	trackview.editor().abort_reversible_command();
1029 	clear_selection_internal ();
1030 }
1031 
1032 NoteBase*
find_canvas_note(boost::shared_ptr<NoteType> note)1033 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1034 {
1035 
1036 	if (_optimization_iterator != _events.end()) {
1037 		++_optimization_iterator;
1038 	}
1039 
1040 	if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
1041 		return _optimization_iterator->second;
1042 	}
1043 
1044 	_optimization_iterator = _events.find (note);
1045 	if (_optimization_iterator != _events.end()) {
1046 		return _optimization_iterator->second;
1047 	}
1048 
1049 	return 0;
1050 }
1051 
1052 /** This version finds any canvas note matching the supplied note. */
1053 NoteBase*
find_canvas_note(Evoral::event_id_t id)1054 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1055 {
1056 	Events::iterator it;
1057 
1058 	for (it = _events.begin(); it != _events.end(); ++it) {
1059 		if (it->first->id() == id) {
1060 			return it->second;
1061 		}
1062 	}
1063 
1064 	return 0;
1065 }
1066 
1067 boost::shared_ptr<PatchChange>
find_canvas_patch_change(MidiModel::PatchChangePtr p)1068 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1069 {
1070 	PatchChanges::const_iterator f = _patch_changes.find (p);
1071 
1072 	if (f != _patch_changes.end()) {
1073 		return f->second;
1074 	}
1075 
1076 	return boost::shared_ptr<PatchChange>();
1077 }
1078 
1079 boost::shared_ptr<SysEx>
find_canvas_sys_ex(MidiModel::SysExPtr s)1080 MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
1081 {
1082 	SysExes::const_iterator f = _sys_exes.find (s);
1083 
1084 	if (f != _sys_exes.end()) {
1085 		return f->second;
1086 	}
1087 
1088 	return boost::shared_ptr<SysEx>();
1089 }
1090 
1091 void
get_events(Events & e,Evoral::Sequence<Temporal::Beats>::NoteOperator op,uint8_t val,int chan_mask)1092 MidiRegionView::get_events (Events& e, Evoral::Sequence<Temporal::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1093 {
1094 	MidiModel::Notes notes;
1095 	_model->get_notes (notes, op, val, chan_mask);
1096 
1097 	for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1098 		NoteBase* cne = find_canvas_note (*n);
1099 		if (cne) {
1100 			e.insert (make_pair (*n, cne));
1101 		}
1102 	}
1103 }
1104 
1105 void
redisplay_model()1106 MidiRegionView::redisplay_model()
1107 {
1108 	if (_active_notes) {
1109 		// Currently recording
1110 		const samplecnt_t zoom = trackview.editor().get_current_zoom();
1111 		if (zoom != _last_display_zoom) {
1112 			/* Update resolved canvas notes to reflect changes in zoom without
1113 			   touching model.  Leave active notes (with length max) alone since
1114 			   they are being extended. */
1115 			for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1116 				if (i->second->note()->end_time() != std::numeric_limits<Temporal::Beats>::max()) {
1117 					update_note(i->second);
1118 				}
1119 			}
1120 			_last_display_zoom = zoom;
1121 		}
1122 		return;
1123 	}
1124 
1125 	if (!_model) {
1126 		return;
1127 	}
1128 
1129 	for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1130 		_optimization_iterator->second->invalidate();
1131 	}
1132 
1133 	bool empty_when_starting = _events.empty();
1134 	_optimization_iterator = _events.begin();
1135 	MidiModel::Notes missing_notes;
1136 	Note* sus = NULL;
1137 	Hit*  hit = NULL;
1138 
1139 	MidiModel::ReadLock lock(_model->read_lock());
1140 	MidiModel::Notes& notes (_model->notes());
1141 
1142 	NoteBase* cne;
1143 	for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1144 
1145 		boost::shared_ptr<NoteType> note (*n);
1146 		bool visible;
1147 
1148 		if (note_in_region_range (note, visible)) {
1149 			if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1150 				cne->validate ();
1151 				if (visible) {
1152 					cne->show ();
1153 				} else {
1154 					cne->hide ();
1155 				}
1156 			} else {
1157 				missing_notes.insert (note);
1158 			}
1159 		}
1160 	}
1161 
1162 	if (!empty_when_starting) {
1163 		MidiModel::Notes::iterator f;
1164 		for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1165 
1166 			NoteBase* cne = i->second;
1167 
1168 			/* remove note items that are no longer valid */
1169 			if (!cne->valid()) {
1170 
1171 				for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1172 					MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1173 					if (gr) {
1174 						gr->remove_note (cne);
1175 					}
1176 				}
1177 
1178 				delete cne;
1179 				i = _events.erase (i);
1180 
1181 			} else {
1182 				bool visible = cne->item()->visible();
1183 
1184 				if ((sus = dynamic_cast<Note*>(cne))) {
1185 
1186 					if (visible) {
1187 						update_sustained (sus);
1188 					}
1189 
1190 				} else if ((hit = dynamic_cast<Hit*>(cne))) {
1191 
1192 					if (visible) {
1193 						update_hit (hit);
1194 					}
1195 
1196 				}
1197 				++i;
1198 			}
1199 		}
1200 	}
1201 
1202 	for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1203 		boost::shared_ptr<NoteType> note (*n);
1204 		NoteBase* cne;
1205 		bool visible;
1206 
1207 		if (note_in_region_range (note, visible)) {
1208 			if (visible) {
1209 				cne = add_note (note, true);
1210 			} else {
1211 				cne = add_note (note, false);
1212 			}
1213 		} else {
1214 			cne = add_note (note, false);
1215 		}
1216 
1217 		for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1218 			if ((*it) == note->id()) {
1219 				add_to_selection (cne);
1220 			}
1221 		}
1222 	}
1223 
1224 	for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1225 		MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1226 		if (gr && !gr->trackview.hidden()) {
1227 			gr->redisplay_model ();
1228 		}
1229 	}
1230 
1231 	display_sysexes();
1232 	display_patch_changes ();
1233 
1234 	_marked_for_selection.clear ();
1235 	_marked_for_velocity.clear ();
1236 	_pending_note_selection.clear ();
1237 
1238 }
1239 
1240 void
display_patch_changes()1241 MidiRegionView::display_patch_changes ()
1242 {
1243 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1244 	uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1245 
1246 	for (uint8_t i = 0; i < 16; ++i) {
1247 		display_patch_changes_on_channel (i, chn_mask & (1 << i));
1248 	}
1249 }
1250 
1251 /** @param active_channel true to display patch changes fully, false to display
1252  * them `greyed-out' (as on an inactive channel)
1253  */
1254 void
display_patch_changes_on_channel(uint8_t channel,bool active_channel)1255 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1256 {
1257 	for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1258 		boost::shared_ptr<PatchChange> p;
1259 
1260 		if ((*i)->channel() != channel) {
1261 			continue;
1262 		}
1263 
1264 		if ((p = find_canvas_patch_change (*i)) != 0) {
1265 
1266 			const samplecnt_t region_samples = source_beats_to_region_samples ((*i)->time());
1267 
1268 			if (region_samples < 0 || region_samples >= _region->length()) {
1269 				p->hide();
1270 			} else {
1271 				const double x = trackview.editor().sample_to_pixel (region_samples);
1272 				p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1273 				p->update_name ();
1274 
1275 				p->show();
1276 			}
1277 
1278 		} else {
1279 			add_canvas_patch_change (*i);
1280 		}
1281 	}
1282 }
1283 
1284 void
display_sysexes()1285 MidiRegionView::display_sysexes()
1286 {
1287 	bool have_periodic_system_messages = false;
1288 	bool display_periodic_messages = true;
1289 
1290 	if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1291 
1292 		for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1293 			if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1294 				have_periodic_system_messages = true;
1295 				break;
1296 			}
1297 		}
1298 
1299 		if (have_periodic_system_messages) {
1300 			double zoom = trackview.editor().get_current_zoom (); // samples per pixel
1301 
1302 			/* get an approximate value for the number of samples per video frame */
1303 
1304 			double video_frame = trackview.session()->sample_rate() * (1.0/30);
1305 
1306 			/* if we are zoomed out beyond than the cutoff (i.e. more
1307 			 * samples per pixel than samples per 4 video frames), don't
1308 			 * show periodic sysex messages.
1309 			 */
1310 
1311 			if (zoom > (video_frame*4)) {
1312 				display_periodic_messages = false;
1313 			}
1314 		}
1315 	} else {
1316 		display_periodic_messages = false;
1317 	}
1318 
1319 	const boost::shared_ptr<MidiRegion> mregion (midi_region());
1320 
1321 	for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1322 		MidiModel::SysExPtr sysex_ptr = *i;
1323 		Temporal::Beats time = sysex_ptr->time();
1324 
1325 		if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1326 			if (!display_periodic_messages) {
1327 				continue;
1328 			}
1329 		}
1330 
1331 		ostringstream str;
1332 		str << hex;
1333 		for (uint32_t b = 0; b < (*i)->size(); ++b) {
1334 			str << int((*i)->buffer()[b]);
1335 			if (b != (*i)->size() -1) {
1336 				str << " ";
1337 			}
1338 		}
1339 		string text = str.str();
1340 
1341 		const double x = trackview.editor().sample_to_pixel(source_beats_to_region_samples(time));
1342 
1343 		double height = midi_stream_view()->contents_height();
1344 
1345 		// CAIROCANVAS: no longer passing *i (the sysex event) to the
1346 		// SysEx canvas object!!!
1347 		boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
1348 
1349 		if (!sysex) {
1350 			sysex = boost::shared_ptr<SysEx>(
1351 				new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
1352 			_sys_exes.insert (make_pair (sysex_ptr, sysex));
1353 		} else {
1354 			sysex->set_height (height);
1355 			sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
1356 		}
1357 
1358 		// Show unless message is beyond the region bounds
1359 		if (time - mregion->start_beats() >= mregion->length_beats() || time < mregion->start_beats()) {
1360 			sysex->hide();
1361 		} else {
1362 			sysex->show();
1363 		}
1364 	}
1365 }
1366 
~MidiRegionView()1367 MidiRegionView::~MidiRegionView ()
1368 {
1369 	in_destructor = true;
1370 
1371 	hide_verbose_cursor ();
1372 
1373 	delete _list_editor;
1374 
1375 	RegionViewGoingAway (this); /* EMIT_SIGNAL */
1376 
1377 	if (_active_notes) {
1378 		end_write();
1379 	}
1380 	_entered_note = 0;
1381 	clear_events ();
1382 
1383 	delete _note_group;
1384 	delete _note_diff_command;
1385 	delete _step_edit_cursor;
1386 }
1387 
1388 void
region_resized(const PropertyChange & what_changed)1389 MidiRegionView::region_resized (const PropertyChange& what_changed)
1390 {
1391 	RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1392 
1393 	if (what_changed.contains (ARDOUR::Properties::position)) {
1394 		_region_relative_time_converter.set_origin_b(_region->position());
1395 		_region_relative_time_converter_double.set_origin_b(_region->position());
1396 		/* reset_width dependent_items() redisplays model */
1397 
1398 	}
1399 
1400 	if (what_changed.contains (ARDOUR::Properties::start) ||
1401 	    what_changed.contains (ARDOUR::Properties::position)) {
1402 		_source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1403 	}
1404 	/* catch end and start trim so we can update the view*/
1405 	if (!what_changed.contains (ARDOUR::Properties::start) &&
1406 	    what_changed.contains (ARDOUR::Properties::length)) {
1407 		enable_display (true);
1408 	} else if (what_changed.contains (ARDOUR::Properties::start) &&
1409 	    what_changed.contains (ARDOUR::Properties::length)) {
1410 		enable_display (true);
1411 	}
1412 }
1413 
1414 void
reset_width_dependent_items(double pixel_width)1415 MidiRegionView::reset_width_dependent_items (double pixel_width)
1416 {
1417 	RegionView::reset_width_dependent_items(pixel_width);
1418 
1419 	if (_enable_display) {
1420 		redisplay_model();
1421 	}
1422 
1423 	bool hide_all = false;
1424 	PatchChanges::iterator x = _patch_changes.begin();
1425 	if (x != _patch_changes.end()) {
1426 		hide_all = x->second->width() >= _pixel_width;
1427 	}
1428 
1429 	if (hide_all) {
1430 		for (; x != _patch_changes.end(); ++x) {
1431 			x->second->hide();
1432 		}
1433 	}
1434 
1435 	move_step_edit_cursor (_step_edit_cursor_position);
1436 	set_step_edit_cursor_width (_step_edit_cursor_width);
1437 }
1438 
1439 void
set_height(double height)1440 MidiRegionView::set_height (double height)
1441 {
1442 	double old_height = _height;
1443 	RegionView::set_height(height);
1444 
1445 	apply_note_range (midi_stream_view()->lowest_note(),
1446 			  midi_stream_view()->highest_note(),
1447 			  height != old_height);
1448 
1449 	if (name_text) {
1450 		name_text->raise_to_top();
1451 	}
1452 
1453 	for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1454 		(*x).second->set_height (midi_stream_view()->contents_height());
1455 	}
1456 
1457 	if (_step_edit_cursor) {
1458 		_step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1459 	}
1460 }
1461 
1462 
1463 /** Apply the current note range from the stream view
1464  * by repositioning/hiding notes as necessary
1465  */
1466 void
apply_note_range(uint8_t min,uint8_t max,bool force)1467 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1468 {
1469 	if (!_enable_display) {
1470 		return;
1471 	}
1472 
1473 	if (!force && _current_range_min == min && _current_range_max == max) {
1474 		return;
1475 	}
1476 
1477 	_current_range_min = min;
1478 	_current_range_max = max;
1479 
1480 	redisplay_model ();
1481 }
1482 
1483 GhostRegion*
add_ghost(TimeAxisView & tv)1484 MidiRegionView::add_ghost (TimeAxisView& tv)
1485 {
1486 	double unit_position = _region->position () / samples_per_pixel;
1487 	MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1488 	MidiGhostRegion* ghost;
1489 
1490 	if (mtv && mtv->midi_view()) {
1491 		/* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1492 		   to allow having midi notes on top of note lines and waveforms.
1493 		*/
1494 		ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1495 	} else {
1496 		ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1497 	}
1498 
1499 	ghost->set_colors ();
1500 	ghost->set_height ();
1501 	ghost->set_duration (_region->length() / samples_per_pixel);
1502 
1503 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1504 		ghost->add_note(i->second);
1505 	}
1506 
1507 	ghosts.push_back (ghost);
1508 	enable_display (true);
1509 	return ghost;
1510 }
1511 
1512 
1513 /** Begin tracking note state for successive calls to add_event
1514  */
1515 void
begin_write()1516 MidiRegionView::begin_write()
1517 {
1518 	if (_active_notes) {
1519 		delete[] _active_notes;
1520 	}
1521 	_active_notes = new Note*[128];
1522 	for (unsigned i = 0; i < 128; ++i) {
1523 		_active_notes[i] = 0;
1524 	}
1525 }
1526 
1527 
1528 /** Destroy note state for add_event
1529  */
1530 void
end_write()1531 MidiRegionView::end_write()
1532 {
1533 	delete[] _active_notes;
1534 	_active_notes = 0;
1535 	_marked_for_selection.clear();
1536 	_marked_for_velocity.clear();
1537 }
1538 
1539 
1540 /** Resolve an active MIDI note (while recording).
1541  */
1542 void
resolve_note(uint8_t note,Temporal::Beats end_time)1543 MidiRegionView::resolve_note (uint8_t note, Temporal::Beats end_time)
1544 {
1545 	if (midi_view()->note_mode() != Sustained) {
1546 		return;
1547 	}
1548 
1549 	if (_active_notes && _active_notes[note]) {
1550 		/* Set note length so update_note() works.  Note this is a local note
1551 		   for recording, not from a model, so we can safely mess with it. */
1552 		_active_notes[note]->note()->set_length (end_time - _active_notes[note]->note()->time());
1553 
1554 		/* End time is relative to the region being recorded. */
1555 		const samplepos_t end_time_samples = region_beats_to_region_samples (end_time);
1556 
1557 		_active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_samples));
1558 		_active_notes[note]->set_outline_all ();
1559 		_active_notes[note] = 0;
1560 	}
1561 }
1562 
1563 
1564 /** Extend active notes to rightmost edge of region (if length is changed)
1565  */
1566 void
extend_active_notes()1567 MidiRegionView::extend_active_notes()
1568 {
1569 	if (!_active_notes) {
1570 		return;
1571 	}
1572 
1573 	for (unsigned i = 0; i < 128; ++i) {
1574 		if (_active_notes[i]) {
1575 			_active_notes[i]->set_x1 (trackview.editor().sample_to_pixel(_region->length()));
1576 		}
1577 	}
1578 }
1579 
1580 void
play_midi_note(boost::shared_ptr<NoteType> note)1581 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1582 {
1583 	if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1584 		return;
1585 	}
1586 
1587 	RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1588 
1589 	if (!route_ui || !route_ui->midi_track()) {
1590 		return;
1591 	}
1592 
1593 	NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1594 	np->add (note);
1595 	np->play ();
1596 
1597 	/* NotePlayer deletes itself */
1598 }
1599 
1600 void
start_playing_midi_note(boost::shared_ptr<NoteType> note)1601 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1602 {
1603 	const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1604 	start_playing_midi_chord(notes);
1605 }
1606 
1607 void
start_playing_midi_chord(vector<boost::shared_ptr<NoteType>> notes)1608 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1609 {
1610 	if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1611 		return;
1612 	}
1613 
1614 	RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1615 
1616 	if (!route_ui || !route_ui->midi_track()) {
1617 		return;
1618 	}
1619 
1620 	NotePlayer* player = new NotePlayer (route_ui->midi_track());
1621 
1622 	for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1623 		player->add (*n);
1624 	}
1625 
1626 	player->play ();
1627 }
1628 
1629 
1630 bool
note_in_region_range(const boost::shared_ptr<NoteType> note,bool & visible) const1631 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1632 {
1633 	const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1634 
1635 	/* must compare double explicitly as Beats::operator< rounds to ppqn */
1636 	const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1637 			      note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1638 
1639 	visible = (note->note() >= _current_range_min) &&
1640 		(note->note() <= _current_range_max);
1641 
1642 	return !outside;
1643 }
1644 
1645 void
update_note(NoteBase * note,bool update_ghost_regions)1646 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1647 {
1648 	Note* sus = NULL;
1649 	Hit*  hit = NULL;
1650 	if ((sus = dynamic_cast<Note*>(note))) {
1651 		update_sustained(sus, update_ghost_regions);
1652 	} else if ((hit = dynamic_cast<Hit*>(note))) {
1653 		update_hit(hit, update_ghost_regions);
1654 	}
1655 }
1656 
1657 /** Update a canvas note's size from its model note.
1658  *  @param ev Canvas note to update.
1659  *  @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1660  */
1661 void
update_sustained(Note * ev,bool update_ghost_regions)1662 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1663 {
1664 	TempoMap& map (trackview.session()->tempo_map());
1665 	const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1666 	boost::shared_ptr<NoteType> note = ev->note();
1667 
1668 	const double session_source_start = _region->quarter_note() - mr->start_beats();
1669 	const samplepos_t note_start_samples = map.sample_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1670 
1671 	const double x0 = max (0.,trackview.editor().sample_to_pixel (note_start_samples));
1672 	double x1;
1673 	const double y0 = 1 + floor(note_to_y(note->note()));
1674 	double y1;
1675 
1676 	if (note->length() == Temporal::Beats()) {
1677 
1678 		/* special case actual zero-length notes */
1679 
1680 		x1 = x0 + 1.;
1681 
1682 	} else if (note->end_time() != std::numeric_limits<Temporal::Beats>::max()) {
1683 
1684 		/* normal note */
1685 
1686 		double note_end_time = note->end_time().to_double();
1687 
1688 		if (note->end_time() > mr->start_beats() + mr->length_beats()) {
1689 			note_end_time = mr->start_beats() + mr->length_beats();
1690 		}
1691 
1692 		const samplepos_t note_end_samples = map.sample_at_quarter_note (session_source_start + note_end_time) - _region->position();
1693 
1694 		x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_samples)) - 1;
1695 
1696 	} else {
1697 
1698 		/* nascent note currently being recorded, noteOff has not yet arrived */
1699 
1700 		x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1701 	}
1702 
1703 	y1 = y0 + std::max(1., floor(note_height()) - 1);
1704 
1705 	ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1706 	ev->set_velocity (note->velocity()/127.0);
1707 
1708 	if (note->end_time() == std::numeric_limits<Temporal::Beats>::max())  {
1709 		if (_active_notes && note->note() < 128) {
1710 			Note* const old_rect = _active_notes[note->note()];
1711 			if (old_rect) {
1712 				/* There is an active note on this key, so we have a stuck
1713 				   note.  Finish the old rectangle here. */
1714 				old_rect->set_x1 (x1);
1715 				old_rect->set_outline_all ();
1716 			}
1717 			_active_notes[note->note()] = ev;
1718 		}
1719 		/* outline all but right edge */
1720 		ev->set_outline_what (ArdourCanvas::Rectangle::What (
1721 					      ArdourCanvas::Rectangle::TOP|
1722 					      ArdourCanvas::Rectangle::LEFT|
1723 					      ArdourCanvas::Rectangle::BOTTOM));
1724 	} else {
1725 		/* outline all edges */
1726 		ev->set_outline_all ();
1727 	}
1728 
1729 	// Update color in case velocity has changed
1730 	const uint32_t base_col = ev->base_color();
1731 	ev->set_fill_color(base_col);
1732 	ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1733 
1734 }
1735 
1736 void
update_hit(Hit * ev,bool update_ghost_regions)1737 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1738 {
1739 	boost::shared_ptr<NoteType> note = ev->note();
1740 
1741 	const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1742 	const samplepos_t note_start_samples = trackview.session()->tempo_map().sample_at_quarter_note (note_time_qn) - _region->position();
1743 
1744 	const double x = trackview.editor().sample_to_pixel(note_start_samples);
1745 	const double diamond_size = std::max(1., floor(note_height()) - 2.);
1746 	const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1747 
1748 	// see DnD note in MidiRegionView::apply_note_range() above
1749 	if (y <= 0 || y >= _height) {
1750 		ev->hide();
1751 	} else {
1752 		ev->show();
1753 	}
1754 
1755 	ev->set_position (ArdourCanvas::Duple (x, y));
1756 	ev->set_height (diamond_size);
1757 
1758 	// Update color in case velocity has changed
1759 	const uint32_t base_col = ev->base_color();
1760 	ev->set_fill_color(base_col);
1761 	ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1762 
1763 }
1764 
1765 /** Add a MIDI note to the view (with length).
1766  *
1767  * If in sustained mode, notes with an end at numeric_limits<Beats>::max() will be
1768  * considered active notes, and resolve_note should be called when the
1769  * corresponding note off event arrives, to properly display the note.
1770  */
1771 NoteBase*
add_note(const boost::shared_ptr<NoteType> note,bool visible)1772 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1773 {
1774 	NoteBase* event = 0;
1775 
1776 	if (midi_view()->note_mode() == Sustained) {
1777 
1778 		Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
1779 
1780 		update_sustained (ev_rect);
1781 
1782 		event = ev_rect;
1783 
1784 	} else if (midi_view()->note_mode() == Percussive) {
1785 
1786 		const double diamond_size = std::max(1., floor(note_height()) - 2.);
1787 
1788 		Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
1789 
1790 		update_hit (ev_diamond);
1791 
1792 		event = ev_diamond;
1793 
1794 	} else {
1795 		event = 0;
1796 	}
1797 
1798 	if (event) {
1799 		MidiGhostRegion* gr;
1800 
1801 		for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1802 			if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1803 				gr->add_note(event);
1804 			}
1805 		}
1806 
1807 		if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1808 			note_selected (event, false);
1809 		}
1810 
1811 		if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1812 			event->show_velocity();
1813 		}
1814 
1815 		event->on_channel_selection_change (get_selected_channels());
1816 		_events.insert (make_pair (event->note(), event));
1817 
1818 		if (visible) {
1819 			event->show();
1820 		} else {
1821 			event->hide ();
1822 		}
1823 	}
1824 
1825 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1826 	MidiStreamView* const view = mtv->midi_view();
1827 
1828 	view->update_note_range (note->note());
1829 	return event;
1830 }
1831 
1832 void
step_add_note(uint8_t channel,uint8_t number,uint8_t velocity,Temporal::Beats pos,Temporal::Beats len)1833 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1834                                Temporal::Beats pos, Temporal::Beats len)
1835 {
1836 	boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1837 
1838 	/* potentially extend region to hold new note */
1839 
1840 	samplepos_t end_sample = source_beats_to_absolute_samples (new_note->end_time());
1841 	samplepos_t region_end = _region->last_sample();
1842 
1843 	if (end_sample > region_end) {
1844 		/* XX sets length in beats from audio space. make musical */
1845 		_region->set_length (end_sample - _region->position(), 0);
1846 	}
1847 
1848 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1849 	MidiStreamView* const view = mtv->midi_view();
1850 
1851 	view->update_note_range(new_note->note());
1852 
1853 	_marked_for_selection.clear ();
1854 
1855 	start_note_diff_command (_("step add"));
1856 
1857 	clear_selection_internal ();
1858 	note_diff_add_note (new_note, true, false);
1859 
1860 	apply_diff();
1861 
1862 	// last_step_edit_note = new_note;
1863 }
1864 
1865 void
step_sustain(Temporal::Beats beats)1866 MidiRegionView::step_sustain (Temporal::Beats beats)
1867 {
1868 	change_note_lengths (false, false, beats, false, true);
1869 }
1870 
1871 /** Add a new patch change flag to the canvas.
1872  * @param patch the patch change to add
1873  * @param the text to display in the flag
1874  * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1875  */
1876 void
add_canvas_patch_change(MidiModel::PatchChangePtr patch)1877 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch)
1878 {
1879 	samplecnt_t region_samples = source_beats_to_region_samples (patch->time());
1880 	const double x = trackview.editor().sample_to_pixel (region_samples);
1881 
1882 	double const height = midi_stream_view()->contents_height();
1883 
1884 	// CAIROCANVAS: active_channel info removed from PatcChange constructor
1885 	// so we need to do something more sophisticated to keep its color
1886 	// appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1887 	// up to date.
1888 	boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1889 		new PatchChange(*this, group,
1890 				height, x, 1.0,
1891 				instrument_info(),
1892 				patch,
1893 				_patch_change_outline,
1894 				_patch_change_fill)
1895 		);
1896 
1897 	if (patch_change->item().width() < _pixel_width) {
1898 		// Show unless patch change is beyond the region bounds
1899 		if (region_samples < 0 || region_samples >= _region->length()) {
1900 			patch_change->hide();
1901 		} else {
1902 			patch_change->show();
1903 		}
1904 	} else {
1905 		patch_change->hide ();
1906 	}
1907 
1908 	_patch_changes.insert (make_pair (patch, patch_change));
1909 }
1910 
1911 void
remove_canvas_patch_change(PatchChange * pc)1912 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1913 {
1914 	/* remove the canvas item */
1915 	for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1916 		if (x->second->patch() == pc->patch()) {
1917 			_patch_changes.erase (x);
1918 			break;
1919 		}
1920 	}
1921 }
1922 
1923 MIDI::Name::PatchPrimaryKey
patch_change_to_patch_key(MidiModel::PatchChangePtr p)1924 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1925 {
1926 	return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1927 }
1928 
1929 /// Return true iff @p pc applies to the given time on the given channel.
1930 static bool
patch_applies(const ARDOUR::MidiModel::constPatchChangePtr pc,Temporal::Beats time,uint8_t channel)1931 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats time, uint8_t channel)
1932 {
1933 	return pc->time() <= time && pc->channel() == channel;
1934 }
1935 
1936 void
get_patch_key_at(Temporal::Beats time,uint8_t channel,MIDI::Name::PatchPrimaryKey & key) const1937 MidiRegionView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1938 {
1939 	// The earliest event not before time
1940 	MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1941 
1942 	// Go backwards until we find the latest PC for this channel, or the start
1943 	while (i != _model->patch_changes().begin() &&
1944 	       (i == _model->patch_changes().end() ||
1945 	        !patch_applies(*i, time, channel))) {
1946 		--i;
1947 	}
1948 
1949 	if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1950 		key.set_bank((*i)->bank());
1951 		key.set_program((*i)->program ());
1952 	} else {
1953 		key.set_bank(0);
1954 		key.set_program(0);
1955 	}
1956 }
1957 
1958 void
change_patch_change(PatchChange & pc,const MIDI::Name::PatchPrimaryKey & new_patch)1959 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1960 {
1961 	string name = _("alter patch change");
1962 	trackview.editor().begin_reversible_command (name);
1963 
1964 	MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1965 
1966 	if (pc.patch()->program() != new_patch.program()) {
1967 		c->change_program (pc.patch (), new_patch.program());
1968 	}
1969 
1970 	int const new_bank = new_patch.bank();
1971 	if (pc.patch()->bank() != new_bank) {
1972 		c->change_bank (pc.patch (), new_bank);
1973 	}
1974 
1975 	_model->apply_command (*trackview.session(), c);
1976 	trackview.editor().commit_reversible_command ();
1977 
1978 	remove_canvas_patch_change (&pc);
1979 	display_patch_changes ();
1980 }
1981 
1982 void
change_patch_change(MidiModel::PatchChangePtr old_change,const Evoral::PatchChange<Temporal::Beats> & new_change)1983 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Temporal::Beats> & new_change)
1984 {
1985 	string name = _("alter patch change");
1986 	trackview.editor().begin_reversible_command (name);
1987 	MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1988 
1989 	if (old_change->time() != new_change.time()) {
1990 		c->change_time (old_change, new_change.time());
1991 	}
1992 
1993 	if (old_change->channel() != new_change.channel()) {
1994 		c->change_channel (old_change, new_change.channel());
1995 	}
1996 
1997 	if (old_change->program() != new_change.program()) {
1998 		c->change_program (old_change, new_change.program());
1999 	}
2000 
2001 	if (old_change->bank() != new_change.bank()) {
2002 		c->change_bank (old_change, new_change.bank());
2003 	}
2004 
2005 	_model->apply_command (*trackview.session(), c);
2006 	trackview.editor().commit_reversible_command ();
2007 
2008 	for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2009 		if (x->second->patch() == old_change) {
2010 			_patch_changes.erase (x);
2011 			break;
2012 		}
2013 	}
2014 
2015 	display_patch_changes ();
2016 }
2017 
2018 /** Add a patch change to the region.
2019  *  @param t Time in samples relative to region position
2020  *  @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2021  *  MidiTimeAxisView::get_channel_for_add())
2022  */
2023 void
add_patch_change(samplecnt_t t,Evoral::PatchChange<Temporal::Beats> const & patch)2024 MidiRegionView::add_patch_change (samplecnt_t t, Evoral::PatchChange<Temporal::Beats> const & patch)
2025 {
2026 	string name = _("add patch change");
2027 
2028 	trackview.editor().begin_reversible_command (name);
2029 	MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2030 	c->add (MidiModel::PatchChangePtr (
2031 		        new Evoral::PatchChange<Temporal::Beats> (
2032 			        absolute_samples_to_source_beats (_region->position() + t),
2033 				patch.channel(), patch.program(), patch.bank()
2034 				)
2035 			)
2036 		);
2037 
2038 	_model->apply_command (*trackview.session(), c);
2039 	trackview.editor().commit_reversible_command ();
2040 
2041 	display_patch_changes ();
2042 }
2043 
2044 void
move_patch_change(PatchChange & pc,Temporal::Beats t)2045 MidiRegionView::move_patch_change (PatchChange& pc, Temporal::Beats t)
2046 {
2047 	trackview.editor().begin_reversible_command (_("move patch change"));
2048 	MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2049 	c->change_time (pc.patch (), t);
2050 	_model->apply_command (*trackview.session(), c);
2051 	trackview.editor().commit_reversible_command ();
2052 
2053 	display_patch_changes ();
2054 }
2055 
2056 void
delete_patch_change(PatchChange * pc)2057 MidiRegionView::delete_patch_change (PatchChange* pc)
2058 {
2059 	trackview.editor().begin_reversible_command (_("delete patch change"));
2060 
2061 	MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2062 	c->remove (pc->patch ());
2063 	_model->apply_command (*trackview.session(), c);
2064 	trackview.editor().commit_reversible_command ();
2065 
2066 	remove_canvas_patch_change (pc);
2067 	display_patch_changes ();
2068 }
2069 
2070 void
step_patch(PatchChange & patch,bool bank,int delta)2071 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2072 {
2073 	MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2074 	if (bank) {
2075 		key.set_bank(key.bank() + delta);
2076 	} else {
2077 		key.set_program(key.program() + delta);
2078 	}
2079 	change_patch_change(patch, key);
2080 }
2081 
2082 void
note_deleted(NoteBase * cne)2083 MidiRegionView::note_deleted (NoteBase* cne)
2084 {
2085 	if (_entered_note && cne == _entered_note) {
2086 		_entered_note = 0;
2087 	}
2088 
2089 	if (_selection.empty()) {
2090 		return;
2091 	}
2092 
2093 	_selection.erase (cne);
2094 }
2095 
2096 void
delete_selection()2097 MidiRegionView::delete_selection()
2098 {
2099 	if (_selection.empty()) {
2100 		return;
2101 	}
2102 
2103 	if (trackview.editor().drags()->active()) {
2104 		return;
2105 	}
2106 
2107 	start_note_diff_command (_("delete selection"));
2108 
2109 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2110 		if ((*i)->selected()) {
2111 			_note_diff_command->remove((*i)->note());
2112 		}
2113 	}
2114 
2115 	_selection.clear();
2116 
2117 	apply_diff ();
2118 
2119 	hide_verbose_cursor ();
2120 }
2121 
2122 void
delete_note(boost::shared_ptr<NoteType> n)2123 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2124 {
2125 	start_note_diff_command (_("delete note"));
2126 	_note_diff_command->remove (n);
2127 	apply_diff ();
2128 
2129 	hide_verbose_cursor ();
2130 }
2131 
2132 void
clear_selection()2133 MidiRegionView::clear_selection ()
2134 {
2135 	clear_note_selection ();
2136 	_mouse_state = None;
2137 }
2138 
2139 void
clear_selection_internal()2140 MidiRegionView::clear_selection_internal ()
2141 {
2142 	DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2143 
2144 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2145 		(*i)->set_selected(false);
2146 		(*i)->hide_velocity();
2147 	}
2148 	_selection.clear();
2149 }
2150 
2151 void
clear_note_selection()2152 MidiRegionView::clear_note_selection ()
2153 {
2154 	clear_selection_internal ();
2155 	PublicEditor& editor(trackview.editor());
2156 	editor.get_selection().remove (this);
2157 }
2158 
2159 void
unique_select(NoteBase * ev)2160 MidiRegionView::unique_select(NoteBase* ev)
2161 {
2162 	clear_selection ();
2163 	add_to_selection(ev);
2164 }
2165 
2166 void
select_all_notes()2167 MidiRegionView::select_all_notes ()
2168 {
2169 	PBD::Unwinder<bool> uw (_no_sound_notes, true);
2170 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2171 		add_to_selection (i->second);
2172 	}
2173 }
2174 
2175 void
select_range(samplepos_t start,samplepos_t end)2176 MidiRegionView::select_range (samplepos_t start, samplepos_t end)
2177 {
2178 	PBD::Unwinder<bool> uw (_no_sound_notes, true);
2179 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2180 		samplepos_t t = source_beats_to_absolute_samples(i->first->time());
2181 		if (t >= start && t <= end) {
2182 			add_to_selection (i->second);
2183 		}
2184 	}
2185 }
2186 
2187 void
extend_selection()2188 MidiRegionView::extend_selection ()
2189 {
2190 	if (_selection.empty()) {
2191 		return;
2192 	}
2193 
2194 	PBD::Unwinder<bool> uw (_no_sound_notes, true);
2195 
2196 	/* find end of current selection */
2197 
2198 	/* XXX NUTEMPO WARNING */
2199 	samplepos_t first_note_start = max_samplepos;
2200 
2201 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2202 		samplepos_t e = source_beats_to_absolute_samples ((*i)->note()->time());
2203 		if (e < first_note_start) {
2204 			first_note_start = e;
2205 		}
2206 	}
2207 
2208 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2209 		samplepos_t t = source_beats_to_absolute_samples(i->first->time());
2210 
2211 		if (i->second->selected()) {
2212 			continue;
2213 		}
2214 
2215 		if (t >= first_note_start) {
2216 			add_to_selection (i->second);
2217 		}
2218 	}
2219 }
2220 
2221 void
invert_selection()2222 MidiRegionView::invert_selection ()
2223 {
2224 	PBD::Unwinder<bool> uw (_no_sound_notes, true);
2225 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2226 		if (i->second->selected()) {
2227 			remove_from_selection(i->second);
2228 		} else {
2229 			add_to_selection (i->second);
2230 		}
2231 	}
2232 }
2233 
2234 /** Used for selection undo/redo.
2235     The requested notes most likely won't exist in the view until the next model redisplay.
2236 */
2237 void
select_notes(list<Evoral::event_id_t> notes,bool allow_audition)2238 MidiRegionView::select_notes (list<Evoral::event_id_t> notes, bool allow_audition)
2239 {
2240 	NoteBase* cne;
2241 	list<Evoral::event_id_t>::iterator n;
2242 
2243 	PBD::Unwinder<bool> uw (_no_sound_notes, allow_audition ? _no_sound_notes : true);
2244 
2245 	for (n = notes.begin(); n != notes.end(); ++n) {
2246 		if ((cne = find_canvas_note(*n)) != 0) {
2247 			add_to_selection (cne);
2248 		} else {
2249 			_pending_note_selection.insert(*n);
2250 		}
2251 	}
2252 }
2253 
2254 void
select_matching_notes(uint8_t notenum,uint16_t channel_mask,bool add,bool extend)2255 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2256 {
2257 	bool have_selection = !_selection.empty();
2258 	uint8_t low_note = 127;
2259 	uint8_t high_note = 0;
2260 	MidiModel::Notes& notes (_model->notes());
2261 	_optimization_iterator = _events.begin();
2262 
2263 	if (extend && !have_selection) {
2264 		extend = false;
2265 	}
2266 
2267 	/* scan existing selection to get note range */
2268 
2269 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2270 		if ((*i)->note()->note() < low_note) {
2271 			low_note = (*i)->note()->note();
2272 		}
2273 		if ((*i)->note()->note() > high_note) {
2274 			high_note = (*i)->note()->note();
2275 		}
2276 	}
2277 
2278 	if (!add) {
2279 
2280 		if (!extend && (low_note == high_note) && (high_note == notenum)) {
2281 			/* only note previously selected is the one we are
2282 			 * reselecting. treat this as cancelling the selection.
2283 			 */
2284 			return;
2285 		}
2286 	}
2287 
2288 	if (extend) {
2289 		low_note = min (low_note, notenum);
2290 		high_note = max (high_note, notenum);
2291 	}
2292 
2293 	PBD::Unwinder<bool> uw (_no_sound_notes, true);
2294 
2295 	for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2296 
2297 		boost::shared_ptr<NoteType> note (*n);
2298 		NoteBase* cne;
2299 		bool select = false;
2300 
2301 		if (((1 << note->channel()) & channel_mask) != 0) {
2302 			if (extend) {
2303 				if ((note->note() >= low_note && note->note() <= high_note)) {
2304 					select = true;
2305 				}
2306 			} else if (note->note() == notenum) {
2307 				select = true;
2308 			}
2309 		}
2310 
2311 		if (select) {
2312 			if ((cne = find_canvas_note (note)) != 0) {
2313 				// extend is false because we've taken care of it,
2314 				// since it extends by time range, not pitch.
2315 				note_selected (cne, add, false);
2316 			}
2317 		}
2318 
2319 		add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2320 
2321 	}
2322 }
2323 
2324 void
toggle_matching_notes(uint8_t notenum,uint16_t channel_mask)2325 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2326 {
2327 	MidiModel::Notes& notes (_model->notes());
2328 	_optimization_iterator = _events.begin();
2329 
2330 	for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2331 
2332 		boost::shared_ptr<NoteType> note (*n);
2333 		NoteBase* cne;
2334 
2335 		if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2336 			if ((cne = find_canvas_note (note)) != 0) {
2337 				if (cne->selected()) {
2338 					note_deselected (cne);
2339 				} else {
2340 					note_selected (cne, true, false);
2341 				}
2342 			}
2343 		}
2344 	}
2345 }
2346 
2347 void
note_selected(NoteBase * ev,bool add,bool extend)2348 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2349 {
2350 	if (!add) {
2351 		clear_selection_internal ();
2352 		add_to_selection (ev);
2353 	}
2354 
2355 	if (!extend) {
2356 
2357 		if (!ev->selected()) {
2358 			add_to_selection (ev);
2359 		}
2360 
2361 	} else {
2362 		/* find end of latest note selected, select all between that and the start of "ev" */
2363 
2364 		Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2365 		Temporal::Beats latest   = Temporal::Beats();
2366 
2367 		for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2368 			if ((*i)->note()->end_time() > latest) {
2369 				latest = (*i)->note()->end_time();
2370 			}
2371 			if ((*i)->note()->time() < earliest) {
2372 				earliest = (*i)->note()->time();
2373 			}
2374 		}
2375 
2376 		if (ev->note()->end_time() > latest) {
2377 			latest = ev->note()->end_time();
2378 		}
2379 
2380 		if (ev->note()->time() < earliest) {
2381 			earliest = ev->note()->time();
2382 		}
2383 
2384 		for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2385 
2386 			/* find notes entirely within OR spanning the earliest..latest range */
2387 
2388 			if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
2389 			    (i->first->time() <= earliest && i->first->end_time() >= latest)) {
2390 				add_to_selection (i->second);
2391 			}
2392 		}
2393 	}
2394 }
2395 
2396 void
note_deselected(NoteBase * ev)2397 MidiRegionView::note_deselected(NoteBase* ev)
2398 {
2399 	remove_from_selection (ev);
2400 }
2401 
2402 void
update_drag_selection(samplepos_t start,samplepos_t end,double gy0,double gy1,bool extend)2403 MidiRegionView::update_drag_selection(samplepos_t start, samplepos_t end, double gy0, double gy1, bool extend)
2404 {
2405 	PublicEditor& editor = trackview.editor();
2406 
2407 	// Convert to local coordinates
2408 	const samplepos_t p  = _region->position();
2409 	const double     y  = midi_view()->y_position();
2410 	const double     x0 = editor.sample_to_pixel(max((samplepos_t)0, start - p));
2411 	const double     x1 = editor.sample_to_pixel(max((samplepos_t)0, end - p));
2412 	const double     y0 = max(0.0, gy0 - y);
2413 	const double     y1 = max(0.0, gy1 - y);
2414 
2415 	// TODO: Make this faster by storing the last updated selection rect, and only
2416 	// adjusting things that are in the area that appears/disappeared.
2417 	// We probably need a tree to be able to find events in O(log(n)) time.
2418 
2419 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2420 		if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
2421 			// Rectangles intersect
2422 			if (!i->second->selected()) {
2423 				add_to_selection (i->second);
2424 			}
2425 		} else if (i->second->selected() && !extend) {
2426 			// Rectangles do not intersect
2427 			remove_from_selection (i->second);
2428 		}
2429 	}
2430 
2431 	typedef RouteTimeAxisView::AutomationTracks ATracks;
2432 	typedef std::list<Selectable*>              Selectables;
2433 
2434 	/* Add control points to selection. */
2435 	const ATracks& atracks = midi_view()->automation_tracks();
2436 	Selectables    selectables;
2437 	editor.get_selection().clear_points();
2438 	for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2439 		a->second->get_selectables(start, end, gy0, gy1, selectables);
2440 		for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2441 			ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2442 			if (cp) {
2443 				editor.get_selection().add(cp);
2444 			}
2445 		}
2446 		a->second->set_selected_points(editor.get_selection().points);
2447 	}
2448 }
2449 
2450 void
update_vertical_drag_selection(double y1,double y2,bool extend)2451 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2452 {
2453 	if (y1 > y2) {
2454 		swap (y1, y2);
2455 	}
2456 
2457 	// TODO: Make this faster by storing the last updated selection rect, and only
2458 	// adjusting things that are in the area that appears/disappeared.
2459 	// We probably need a tree to be able to find events in O(log(n)) time.
2460 
2461 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2462 		if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
2463 			// within y- (note-) range
2464 			if (!i->second->selected()) {
2465 				add_to_selection (i->second);
2466 			}
2467 		} else if (i->second->selected() && !extend) {
2468 			remove_from_selection (i->second);
2469 		}
2470 	}
2471 }
2472 
2473 void
remove_from_selection(NoteBase * ev)2474 MidiRegionView::remove_from_selection (NoteBase* ev)
2475 {
2476 	Selection::iterator i = _selection.find (ev);
2477 
2478 	if (i != _selection.end()) {
2479 		_selection.erase (i);
2480 	}
2481 
2482 	ev->set_selected (false);
2483 	ev->hide_velocity ();
2484 
2485 	if (_selection.empty()) {
2486 		PublicEditor& editor (trackview.editor());
2487 		editor.get_selection().remove (this);
2488 	}
2489 }
2490 
2491 void
add_to_selection(NoteBase * ev)2492 MidiRegionView::add_to_selection (NoteBase* ev)
2493 {
2494 	if (_selection.empty()) {
2495 
2496 		/* we're about to select a note/some notes. Obey rule that only
2497 		 * 1 thing can be selected by clearing any current selection
2498 		 */
2499 
2500 		trackview.editor().get_selection().clear ();
2501 
2502 		/* first note selected in this region, force Editor region
2503 		 * selection to this region.
2504 		 *
2505 		 * this breaks the "only 1 type of thing selected" rule, but
2506 		 * having the region selected allows "operations applied to
2507 		 * selected MIDI regions" to work. And we can only select notes
2508 		 * when in internal edit mode, so we know that operations will
2509 		 * only apply to notes anyway, not regions.
2510 		 */
2511 
2512 		trackview.editor().set_selected_midi_region_view (*this);
2513 	}
2514 
2515 	if (_selection.insert (ev).second == true) {
2516 		ev->set_selected (true);
2517 		start_playing_midi_note ((ev)->note());
2518 	}
2519 }
2520 
2521 Temporal::Beats
earliest_in_selection()2522 MidiRegionView::earliest_in_selection ()
2523 {
2524 	Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2525 
2526 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2527 		if ((*i)->note()->time() < earliest) {
2528 			earliest = (*i)->note()->time();
2529 		}
2530 	}
2531 
2532 	return earliest;
2533 }
2534 
2535 void
move_selection(double dx_qn,double dy,double cumulative_dy)2536 MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
2537 {
2538 	typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2539 	Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2540 	TempoMap& tmap (editor->session()->tempo_map());
2541 	PossibleChord to_play;
2542 	Temporal::Beats earliest = earliest_in_selection();
2543 
2544 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2545 		NoteBase* n = *i;
2546 		if (n->note()->time() == earliest) {
2547 			to_play.push_back (n->note());
2548 		}
2549 		double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2550 		double dx = 0.0;
2551 		if (midi_view()->note_mode() == Sustained) {
2552 			dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2553 				- n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2554 		} else {
2555 			/* Hit::x0() is offset by _position.x, unlike Note::x0() */
2556 			Hit* hit = dynamic_cast<Hit*>(n);
2557 			if (hit) {
2558 				dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2559 					- n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2560 			}
2561 		}
2562 
2563 		(*i)->move_event(dx, dy);
2564 
2565 		/* update length */
2566 		if (midi_view()->note_mode() == Sustained) {
2567 			Note* sus = dynamic_cast<Note*> (*i);
2568 			double const len_dx = editor->sample_to_pixel_unrounded (
2569 				tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2570 
2571 			sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2572 		}
2573 	}
2574 
2575 	if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2576 
2577 		if (to_play.size() > 1) {
2578 
2579 			PossibleChord shifted;
2580 
2581 			for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2582 				boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2583 				moved_note->set_note (moved_note->note() + cumulative_dy);
2584 				shifted.push_back (moved_note);
2585 			}
2586 
2587 			start_playing_midi_chord (shifted);
2588 
2589 		} else if (!to_play.empty()) {
2590 
2591 			boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2592 			moved_note->set_note (moved_note->note() + cumulative_dy);
2593 			start_playing_midi_note (moved_note);
2594 		}
2595 	}
2596 }
2597 
2598 NoteBase*
copy_selection(NoteBase * primary)2599 MidiRegionView::copy_selection (NoteBase* primary)
2600 {
2601 	_copy_drag_events.clear ();
2602 
2603 	if (_selection.empty()) {
2604 		return 0;
2605 	}
2606 
2607 	NoteBase* note;
2608 	NoteBase* ret = 0;
2609 
2610 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2611 		boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
2612 		if (midi_view()->note_mode() == Sustained) {
2613 			Note* n = new Note (*this, _note_group, g);
2614 			update_sustained (n, false);
2615 			note = n;
2616 		} else {
2617 			Hit* h = new Hit (*this, _note_group, 10, g);
2618 			update_hit (h, false);
2619 			note = h;
2620 		}
2621 
2622 		if ((*i) == primary) {
2623 			ret = note;
2624 		}
2625 
2626 		_copy_drag_events.push_back (note);
2627 	}
2628 
2629 	return ret;
2630 }
2631 
2632 void
move_copies(double dx_qn,double dy,double cumulative_dy)2633 MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
2634 {
2635 	typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2636 	Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2637 	TempoMap& tmap (editor->session()->tempo_map());
2638 	PossibleChord to_play;
2639 	Temporal::Beats earliest = earliest_in_selection();
2640 
2641 	for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2642 		NoteBase* n = *i;
2643 		if (n->note()->time() == earliest) {
2644 			to_play.push_back (n->note());
2645 		}
2646 		double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2647 		double dx = 0.0;
2648 		if (midi_view()->note_mode() == Sustained) {
2649 			dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2650 				- n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2651 		} else {
2652 			Hit* hit = dynamic_cast<Hit*>(n);
2653 			if (hit) {
2654 				dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2655 					- n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2656 			}
2657 		}
2658 
2659 		(*i)->move_event(dx, dy);
2660 
2661 		if (midi_view()->note_mode() == Sustained) {
2662 			Note* sus = dynamic_cast<Note*> (*i);
2663 			double const len_dx = editor->sample_to_pixel_unrounded (
2664 				tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2665 
2666 			sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2667 		}
2668 	}
2669 
2670 	if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2671 
2672 		if (to_play.size() > 1) {
2673 
2674 			PossibleChord shifted;
2675 
2676 			for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2677 				boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2678 				moved_note->set_note (moved_note->note() + cumulative_dy);
2679 				shifted.push_back (moved_note);
2680 			}
2681 
2682 			start_playing_midi_chord (shifted);
2683 
2684 		} else if (!to_play.empty()) {
2685 
2686 			boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2687 			moved_note->set_note (moved_note->note() + cumulative_dy);
2688 			start_playing_midi_note (moved_note);
2689 		}
2690 	}
2691 }
2692 
2693 void
note_dropped(NoteBase *,double d_qn,int8_t dnote,bool copy)2694 MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
2695 {
2696 	uint8_t lowest_note_in_selection  = 127;
2697 	uint8_t highest_note_in_selection = 0;
2698 	uint8_t highest_note_difference   = 0;
2699 
2700 	if (!copy) {
2701 		// find highest and lowest notes first
2702 
2703 		for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2704 			uint8_t pitch = (*i)->note()->note();
2705 			lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
2706 			highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2707 		}
2708 
2709 		/*
2710 		  cerr << "dnote: " << (int) dnote << endl;
2711 		  cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2712 		  << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2713 		  cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2714 		  << int(highest_note_in_selection) << endl;
2715 		  cerr << "selection size: " << _selection.size() << endl;
2716 		  cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2717 		*/
2718 
2719 		// Make sure the note pitch does not exceed the MIDI standard range
2720 		if (highest_note_in_selection + dnote > 127) {
2721 			highest_note_difference = highest_note_in_selection - 127;
2722 		}
2723 
2724 		start_note_diff_command (_("move notes"));
2725 
2726 		for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2727 
2728 			Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2729 
2730 			if (new_time < 0) {
2731 				continue;
2732 			}
2733 
2734 			note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2735 
2736 			uint8_t original_pitch = (*i)->note()->note();
2737 			uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
2738 
2739 			// keep notes in standard midi range
2740 			clamp_to_0_127(new_pitch);
2741 
2742 			lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
2743 			highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2744 
2745 			note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2746 		}
2747 	} else {
2748 
2749 		clear_selection_internal ();
2750 
2751 		for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2752 			uint8_t pitch = (*i)->note()->note();
2753 			lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
2754 			highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2755 		}
2756 
2757 		// Make sure the note pitch does not exceed the MIDI standard range
2758 		if (highest_note_in_selection + dnote > 127) {
2759 			highest_note_difference = highest_note_in_selection - 127;
2760 		}
2761 
2762 		start_note_diff_command (_("copy notes"));
2763 
2764 		for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
2765 
2766 			/* update time */
2767 			Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2768 
2769 			if (new_time < 0) {
2770 				continue;
2771 			}
2772 
2773 			(*i)->note()->set_time (new_time);
2774 
2775 			/* update pitch */
2776 
2777 			uint8_t original_pitch = (*i)->note()->note();
2778 			uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
2779 
2780 			(*i)->note()->set_note (new_pitch);
2781 
2782 			// keep notes in standard midi range
2783 			clamp_to_0_127(new_pitch);
2784 
2785 			lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
2786 			highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2787 
2788 			note_diff_add_note ((*i)->note(), true);
2789 
2790 			delete *i;
2791 		}
2792 
2793 		_copy_drag_events.clear ();
2794 	}
2795 
2796 	apply_diff (false, copy);
2797 
2798 	// care about notes being moved beyond the upper/lower bounds on the canvas
2799 	if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
2800 	    highest_note_in_selection > midi_stream_view()->highest_note()) {
2801 		midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
2802 	}
2803 }
2804 
2805 /** @param x Pixel relative to the region position.
2806  *  @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2807  *  Used for inverting the snap logic with key modifiers and snap delta calculation.
2808  *  @return Snapped sample relative to the region position.
2809  */
2810 samplepos_t
snap_pixel_to_sample(double x,bool ensure_snap)2811 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2812 {
2813 	PublicEditor& editor (trackview.editor());
2814 	return snap_sample_to_sample (editor.pixel_to_sample (x), ensure_snap).sample;
2815 }
2816 
2817 /** @param x Pixel relative to the region position.
2818  *  @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2819  *  @return Snapped pixel relative to the region position.
2820  */
2821 double
snap_to_pixel(double x,bool ensure_snap)2822 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2823 {
2824 	return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2825 }
2826 
2827 double
get_position_pixels()2828 MidiRegionView::get_position_pixels()
2829 {
2830 	samplepos_t region_sample = get_position();
2831 	return trackview.editor().sample_to_pixel(region_sample);
2832 }
2833 
2834 double
get_end_position_pixels()2835 MidiRegionView::get_end_position_pixels()
2836 {
2837 	samplepos_t sample = get_position() + get_duration ();
2838 	return trackview.editor().sample_to_pixel(sample);
2839 }
2840 
2841 samplepos_t
source_beats_to_absolute_samples(Temporal::Beats beats) const2842 MidiRegionView::source_beats_to_absolute_samples(Temporal::Beats beats) const
2843 {
2844 	/* the time converter will return the sample corresponding to `beats'
2845 	   relative to the start of the source. The start of the source
2846 	   is an implied position given by region->position - region->start
2847 	*/
2848 	const samplepos_t source_start = _region->position() - _region->start();
2849 	return  source_start +  _source_relative_time_converter.to (beats);
2850 }
2851 
2852 Temporal::Beats
absolute_samples_to_source_beats(samplepos_t samples) const2853 MidiRegionView::absolute_samples_to_source_beats(samplepos_t samples) const
2854 {
2855 	/* the `samples' argument needs to be converted into a sample count
2856 	   relative to the start of the source before being passed in to the
2857 	   converter.
2858 	*/
2859 	const samplepos_t source_start = _region->position() - _region->start();
2860 	return  _source_relative_time_converter.from (samples - source_start);
2861 }
2862 
2863 samplepos_t
region_beats_to_region_samples(Temporal::Beats beats) const2864 MidiRegionView::region_beats_to_region_samples(Temporal::Beats beats) const
2865 {
2866 	return _region_relative_time_converter.to(beats);
2867 }
2868 
2869 Temporal::Beats
region_samples_to_region_beats(samplepos_t samples) const2870 MidiRegionView::region_samples_to_region_beats(samplepos_t samples) const
2871 {
2872 	return _region_relative_time_converter.from(samples);
2873 }
2874 
2875 double
region_samples_to_region_beats_double(samplepos_t samples) const2876 MidiRegionView::region_samples_to_region_beats_double (samplepos_t samples) const
2877 {
2878 	return _region_relative_time_converter_double.from(samples);
2879 }
2880 
2881 void
begin_resizing(bool)2882 MidiRegionView::begin_resizing (bool /*at_front*/)
2883 {
2884 	_resize_data.clear();
2885 
2886 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2887 		Note *note = dynamic_cast<Note*> (*i);
2888 
2889 		// only insert CanvasNotes into the map
2890 		if (note) {
2891 			NoteResizeData *resize_data = new NoteResizeData();
2892 			resize_data->note = note;
2893 
2894 			// create a new SimpleRect from the note which will be the resize preview
2895 			ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2896 											    ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2897 
2898 			// calculate the colors: get the color settings
2899 			uint32_t fill_color = NoteBase::meter_style_fill_color (note->note()->velocity(), true);
2900 
2901 			// make the resize preview notes more transparent and bright
2902 			fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2903 
2904 			// calculate color based on note velocity
2905 			resize_rect->set_fill_color (UINT_INTERPOLATE(
2906 				NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2907 				fill_color,
2908 				0.85));
2909 
2910 			resize_rect->set_outline_color (NoteBase::calculate_outline (
2911 								UIConfiguration::instance().color ("midi note selected outline")));
2912 
2913 			resize_data->resize_rect = resize_rect;
2914 			_resize_data.push_back(resize_data);
2915 		}
2916 	}
2917 }
2918 
2919 /** Update resizing notes while user drags.
2920  * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2921  * @param at_front which end of the note (true == note on, false == note off)
2922  * @param delta_x change in mouse position since the start of the drag
2923  * @param relative true if relative resizing is taking place, false if absolute resizing.  This only makes
2924  * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2925  * amount of the drag.  In non-relative mode, all selected notes are set to have the same start or end point
2926  * as the \a primary note.
2927  * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2928  * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2929  */
2930 void
update_resizing(NoteBase * primary,bool at_front,double delta_x,bool relative,double snap_delta,bool with_snap)2931 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2932 {
2933 	TempoMap& tmap (trackview.session()->tempo_map());
2934 	bool cursor_set = false;
2935 	bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2936 
2937 	for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2938 		ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2939 		Note* canvas_note = (*i)->note;
2940 		double current_x;
2941 
2942 		if (at_front) {
2943 			if (relative) {
2944 				current_x = canvas_note->x0() + delta_x + snap_delta;
2945 			} else {
2946 				current_x = primary->x0() + delta_x + snap_delta;
2947 			}
2948 		} else {
2949 			if (relative) {
2950 				current_x = canvas_note->x1() + delta_x + snap_delta;
2951 			} else {
2952 				current_x = primary->x1() + delta_x + snap_delta;
2953 			}
2954 		}
2955 
2956 		if (current_x < 0) {
2957 			/* This works even with snapping because RegionView::snap_sample_to_sample()
2958 			 * snaps forward if the snapped sample is before the beginning of the region
2959 			 */
2960 			current_x = 0;
2961 		}
2962 		if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2963 			current_x = trackview.editor().sample_to_pixel(_region->length());
2964 		}
2965 
2966 		if (at_front) {
2967 			if (with_snap) {
2968 				resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2969 			} else {
2970 				resize_rect->set_x0 (current_x - snap_delta);
2971 			}
2972 			resize_rect->set_x1 (canvas_note->x1());
2973 		} else {
2974 			if (with_snap) {
2975 				resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2976 			} else {
2977 				resize_rect->set_x1 (current_x - snap_delta);
2978 			}
2979 			resize_rect->set_x0 (canvas_note->x0());
2980 		}
2981 
2982 		if (!cursor_set) {
2983 			/* Convert snap delta from pixels to beats. */
2984 			samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2985 			double snap_delta_beats = 0.0;
2986 			int sign = 1;
2987 
2988 			/* negative beat offsets aren't allowed */
2989 			if (snap_delta_samps > 0) {
2990 				snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
2991 			} else if (snap_delta_samps < 0) {
2992 				snap_delta_beats = region_samples_to_region_beats_double (- snap_delta_samps);
2993 				sign = -1;
2994 			}
2995 
2996 			double  snapped_x;
2997 			int32_t divisions = 0;
2998 
2999 			if (with_snap) {
3000 				snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
3001 				divisions = trackview.editor().get_grid_music_divisions (0);
3002 			} else {
3003 				snapped_x = trackview.editor ().pixel_to_sample (current_x);
3004 			}
3005 
3006 			const Temporal::Beats beats = Temporal::Beats (tmap.exact_beat_at_sample (snapped_x + midi_region()->position(), divisions)
3007 			                                               - midi_region()->beat())
3008 			                              + midi_region()->start_beats();
3009 
3010 			Temporal::Beats len         = Temporal::Beats();
3011 
3012 			if (at_front) {
3013 				if (beats < canvas_note->note()->end_time()) {
3014 					len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
3015 					len += canvas_note->note()->length();
3016 				}
3017 			} else {
3018 				if (beats >= canvas_note->note()->time()) {
3019 					len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
3020 				}
3021 			}
3022 
3023 			/* minimum length resulting from a trim is 1 tick */
3024 			len = std::max (Temporal::Beats (0,1), len);
3025 
3026 			char buf[16];
3027 			snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
3028 			show_verbose_cursor (buf, 0, 0);
3029 
3030 			cursor_set = true;
3031 
3032 			trackview.editor().set_snapped_cursor_position ( snapped_x + midi_region()->position() );
3033 		}
3034 
3035 	}
3036 }
3037 
3038 
3039 /** Finish resizing notes when the user releases the mouse button.
3040  *  Parameters the same as for \a update_resizing().
3041  */
3042 void
commit_resizing(NoteBase * primary,bool at_front,double delta_x,bool relative,double snap_delta,bool with_snap)3043 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
3044 {
3045 	_note_diff_command = _model->new_note_diff_command (_("resize notes"));
3046 	TempoMap& tmap (trackview.session()->tempo_map());
3047 
3048 	/* XX why doesn't snap_pixel_to_sample() handle this properly? */
3049 	bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
3050 
3051 	for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3052 		Note*  canvas_note = (*i)->note;
3053 		ArdourCanvas::Rectangle*  resize_rect = (*i)->resize_rect;
3054 
3055 		/* Get the new x position for this resize, which is in pixels relative
3056 		 * to the region position.
3057 		 */
3058 
3059 		double current_x;
3060 
3061 		if (at_front) {
3062 			if (relative) {
3063 				current_x = canvas_note->x0() + delta_x + snap_delta;
3064 			} else {
3065 				current_x = primary->x0() + delta_x + snap_delta;
3066 			}
3067 		} else {
3068 			if (relative) {
3069 				current_x = canvas_note->x1() + delta_x + snap_delta;
3070 			} else {
3071 				current_x = primary->x1() + delta_x + snap_delta;
3072 			}
3073 		}
3074 
3075 		if (current_x < 0) {
3076 			current_x = 0;
3077 		}
3078 		if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3079 			current_x = trackview.editor().sample_to_pixel(_region->length());
3080 		}
3081 
3082 		/* Convert snap delta from pixels to beats with sign. */
3083 		samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3084 		double snap_delta_beats = 0.0;
3085 		int sign = 1;
3086 
3087 		if (snap_delta_samps > 0) {
3088 			snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3089 		} else if (snap_delta_samps < 0) {
3090 			snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3091 			sign = -1;
3092 		}
3093 
3094 		uint32_t divisions = 0;
3095 		/* Convert the new x position to a sample within the source */
3096 		samplepos_t current_fr;
3097 		if (with_snap) {
3098 			current_fr = snap_pixel_to_sample (current_x, ensure_snap);
3099 			divisions = trackview.editor().get_grid_music_divisions (0);
3100 		} else {
3101 			current_fr = trackview.editor().pixel_to_sample (current_x);
3102 		}
3103 
3104 		/* and then to beats */
3105 		const double e_qaf = tmap.exact_qn_at_sample (current_fr + midi_region()->position(), divisions);
3106 		const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
3107 		const Temporal::Beats x_beats = Temporal::Beats (e_qaf - quarter_note_start);
3108 
3109 		if (at_front && x_beats < canvas_note->note()->end_time()) {
3110 			const Temporal::Beats new_start = x_beats - (sign * snap_delta_beats);
3111 			note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, new_start);
3112 			Temporal::Beats len = canvas_note->note()->end_time() - new_start;
3113 
3114 			if (!!len) {
3115 				note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3116 			}
3117 		}
3118 
3119 		if (!at_front) {
3120 			Temporal::Beats len = std::max (Temporal::Beats(0, 1), x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
3121 			note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3122 		}
3123 
3124 		delete resize_rect;
3125 		delete (*i);
3126 	}
3127 
3128 	_resize_data.clear();
3129 	apply_diff(true);
3130 }
3131 
3132 void
abort_resizing()3133 MidiRegionView::abort_resizing ()
3134 {
3135 	for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3136 		delete (*i)->resize_rect;
3137 		delete *i;
3138 	}
3139 
3140 	_resize_data.clear ();
3141 }
3142 
3143 void
change_note_velocity(NoteBase * event,int8_t velocity,bool relative)3144 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
3145 {
3146 	uint8_t new_velocity;
3147 
3148 	if (relative) {
3149 		new_velocity = event->note()->velocity() + velocity;
3150 		clamp_to_0_127(new_velocity);
3151 	} else {
3152 		new_velocity = velocity;
3153 	}
3154 
3155 	event->set_selected (event->selected()); // change color
3156 
3157 	note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3158 }
3159 
3160 void
change_note_note(NoteBase * event,int8_t note,bool relative)3161 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3162 {
3163 	uint8_t new_note;
3164 
3165 	if (relative) {
3166 		new_note = event->note()->note() + note;
3167 	} else {
3168 		new_note = note;
3169 	}
3170 
3171 	clamp_to_0_127 (new_note);
3172 	note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3173 }
3174 
3175 void
trim_note(NoteBase * event,Temporal::Beats front_delta,Temporal::Beats end_delta)3176 MidiRegionView::trim_note (NoteBase* event, Temporal::Beats front_delta, Temporal::Beats end_delta)
3177 {
3178 	bool change_start = false;
3179 	bool change_length = false;
3180 	Temporal::Beats new_start;
3181 	Temporal::Beats new_length;
3182 
3183 	/* NOTE: the semantics of the two delta arguments are slightly subtle:
3184 
3185 	   front_delta: if positive - move the start of the note later in time (shortening it)
3186 	   if negative - move the start of the note earlier in time (lengthening it)
3187 
3188 	   end_delta:   if positive - move the end of the note later in time (lengthening it)
3189 	   if negative - move the end of the note earlier in time (shortening it)
3190 	*/
3191 
3192 	if (!!front_delta) {
3193 		if (front_delta < 0) {
3194 
3195 			if (event->note()->time() < -front_delta) {
3196 				new_start = Temporal::Beats();
3197 			} else {
3198 				new_start = event->note()->time() + front_delta; // moves earlier
3199 			}
3200 
3201 			/* start moved toward zero, so move the end point out to where it used to be.
3202 			   Note that front_delta is negative, so this increases the length.
3203 			*/
3204 
3205 			new_length = event->note()->length() - front_delta;
3206 			change_start = true;
3207 			change_length = true;
3208 
3209 		} else {
3210 
3211 			Temporal::Beats new_pos = event->note()->time() + front_delta;
3212 
3213 			if (new_pos < event->note()->end_time()) {
3214 				new_start = event->note()->time() + front_delta;
3215 				/* start moved toward the end, so move the end point back to where it used to be */
3216 				new_length = event->note()->length() - front_delta;
3217 				change_start = true;
3218 				change_length = true;
3219 			}
3220 		}
3221 
3222 	}
3223 
3224 	if (!!end_delta) {
3225 		bool can_change = true;
3226 		if (end_delta < 0) {
3227 			if (event->note()->length() < -end_delta) {
3228 				can_change = false;
3229 			}
3230 		}
3231 
3232 		if (can_change) {
3233 			new_length = event->note()->length() + end_delta;
3234 			change_length = true;
3235 		}
3236 	}
3237 
3238 	if (change_start) {
3239 		note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3240 	}
3241 
3242 	if (change_length) {
3243 		note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3244 	}
3245 }
3246 
3247 void
change_note_channel(NoteBase * event,int8_t chn,bool relative)3248 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3249 {
3250 	uint8_t new_channel;
3251 
3252 	if (relative) {
3253 		if (chn < 0.0) {
3254 			if (event->note()->channel() < -chn) {
3255 				new_channel = 0;
3256 			} else {
3257 				new_channel = event->note()->channel() + chn;
3258 			}
3259 		} else {
3260 			new_channel = event->note()->channel() + chn;
3261 		}
3262 	} else {
3263 		new_channel = (uint8_t) chn;
3264 	}
3265 
3266 	note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3267 }
3268 
3269 void
change_note_time(NoteBase * event,Temporal::Beats delta,bool relative)3270 MidiRegionView::change_note_time (NoteBase* event, Temporal::Beats delta, bool relative)
3271 {
3272 	Temporal::Beats new_time;
3273 
3274 	if (relative) {
3275 		if (delta < 0.0) {
3276 			if (event->note()->time() < -delta) {
3277 				new_time = Temporal::Beats();
3278 			} else {
3279 				new_time = event->note()->time() + delta;
3280 			}
3281 		} else {
3282 			new_time = event->note()->time() + delta;
3283 		}
3284 	} else {
3285 		new_time = delta;
3286 	}
3287 
3288 	note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3289 }
3290 
3291 void
change_note_length(NoteBase * event,Temporal::Beats t)3292 MidiRegionView::change_note_length (NoteBase* event, Temporal::Beats t)
3293 {
3294 	note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3295 }
3296 
3297 void
change_velocities(bool up,bool fine,bool allow_smush,bool all_together)3298 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3299 {
3300 	int8_t delta;
3301 	int8_t value = 0;
3302 
3303 	if (_selection.empty()) {
3304 		return;
3305 	}
3306 
3307 	if (fine) {
3308 		delta = 1;
3309 	} else {
3310 		delta = 10;
3311 	}
3312 
3313 	if (!up) {
3314 		delta = -delta;
3315 	}
3316 
3317 	if (!allow_smush) {
3318 		for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3319 			if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3320 				goto cursor_label;
3321 			}
3322 		}
3323 	}
3324 
3325 	start_note_diff_command (_("change velocities"));
3326 
3327 	for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3328 		Selection::iterator next = i;
3329 		++next;
3330 
3331 		if (all_together) {
3332 			if (i == _selection.begin()) {
3333 				change_note_velocity (*i, delta, true);
3334 				value = (*i)->note()->velocity() + delta;
3335 			} else {
3336 				change_note_velocity (*i, value, false);
3337 			}
3338 
3339 		} else {
3340 			change_note_velocity (*i, delta, true);
3341 		}
3342 
3343 		i = next;
3344 	}
3345 
3346 	apply_diff();
3347 
3348   cursor_label:
3349 	if (!_selection.empty()) {
3350 		char buf[24];
3351 		snprintf (buf, sizeof (buf), "Vel %d",
3352 		          (int) (*_selection.begin())->note()->velocity());
3353 		show_verbose_cursor (buf, 10, 10);
3354 	}
3355 }
3356 
3357 
3358 void
transpose(bool up,bool fine,bool allow_smush)3359 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3360 {
3361 	if (_selection.empty()) {
3362 		return;
3363 	}
3364 
3365 	int8_t delta;
3366 
3367 	if (fine) {
3368 		delta = 1;
3369 	} else {
3370 		delta = 12;
3371 	}
3372 
3373 	if (!up) {
3374 		delta = -delta;
3375 	}
3376 
3377 	if (!allow_smush) {
3378 		for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3379 			if (!up) {
3380 				if ((int8_t) (*i)->note()->note() + delta <= 0) {
3381 					return;
3382 				}
3383 			} else {
3384 				if ((int8_t) (*i)->note()->note() + delta > 127) {
3385 					return;
3386 				}
3387 			}
3388 		}
3389 	}
3390 
3391 	start_note_diff_command (_("transpose"));
3392 
3393 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3394 		Selection::iterator next = i;
3395 		++next;
3396 		change_note_note (*i, delta, true);
3397 		i = next;
3398 	}
3399 
3400 	apply_diff ();
3401 }
3402 
3403 void
change_note_lengths(bool fine,bool shorter,Temporal::Beats delta,bool start,bool end)3404 MidiRegionView::change_note_lengths (bool fine, bool shorter, Temporal::Beats delta, bool start, bool end)
3405 {
3406 	if (!delta) {
3407 		if (fine) {
3408 			delta = Temporal::Beats(1.0/128.0);
3409 		} else {
3410 			/* grab the current grid distance */
3411 			delta = get_grid_beats(_region->position());
3412 		}
3413 	}
3414 
3415 	if (shorter) {
3416 		delta = -delta;
3417 	}
3418 
3419 	start_note_diff_command (_("change note lengths"));
3420 
3421 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3422 		Selection::iterator next = i;
3423 		++next;
3424 
3425 		/* note the negation of the delta for start */
3426 
3427 		trim_note (*i,
3428 		           (start ? -delta : Temporal::Beats()),
3429 		           (end   ? delta  : Temporal::Beats()));
3430 		i = next;
3431 	}
3432 
3433 	apply_diff ();
3434 
3435 }
3436 
3437 void
nudge_notes(bool forward,bool fine)3438 MidiRegionView::nudge_notes (bool forward, bool fine)
3439 {
3440 	if (_selection.empty()) {
3441 		return;
3442 	}
3443 
3444 	/* pick a note as the point along the timeline to get the nudge distance.
3445 	   its not necessarily the earliest note, so we may want to pull the notes out
3446 	   into a vector and sort before using the first one.
3447 	*/
3448 
3449 	const samplepos_t ref_point = source_beats_to_absolute_samples ((*(_selection.begin()))->note()->time());
3450 	Temporal::Beats  delta;
3451 
3452 	if (trackview.editor().snap_mode() == Editing::SnapOff) {
3453 
3454 		/* grid is off - use nudge distance */
3455 
3456 		samplepos_t       unused;
3457 		const samplecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3458 		delta = region_samples_to_region_beats (fabs ((double)distance));
3459 
3460 	} else {
3461 
3462 		/* use grid */
3463 
3464 		bool success;
3465 
3466 		delta = trackview.editor().get_grid_type_as_beats (success, ref_point);
3467 
3468 		if (!success) {
3469 			delta = Temporal::Beats (1, 0);
3470 		}
3471 	}
3472 
3473 	if (!delta) {
3474 		return;
3475 	}
3476 
3477 	if (fine) {
3478 		delta = delta / 4;
3479 	}
3480 
3481 	if (!forward) {
3482 		delta = -delta;
3483 	}
3484 
3485 	start_note_diff_command (_("nudge"));
3486 
3487 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3488 		Selection::iterator next = i;
3489 		++next;
3490 		change_note_time (*i, delta, true);
3491 		i = next;
3492 	}
3493 
3494 	apply_diff ();
3495 }
3496 
3497 void
change_channel(uint8_t channel)3498 MidiRegionView::change_channel(uint8_t channel)
3499 {
3500 	start_note_diff_command(_("change channel"));
3501 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3502 		note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3503 	}
3504 
3505 	apply_diff();
3506 }
3507 
3508 
3509 void
note_entered(NoteBase * ev)3510 MidiRegionView::note_entered(NoteBase* ev)
3511 {
3512 	_entered_note = ev;
3513 
3514 	Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3515 
3516 	if (_mouse_state == SelectTouchDragging) {
3517 
3518 		note_selected (ev, true);
3519 
3520 	} else if (editor->current_mouse_mode() == MouseContent) {
3521 
3522 		remove_ghost_note ();
3523 		show_verbose_cursor (ev->note ());
3524 
3525 	} else if (editor->current_mouse_mode() == MouseDraw) {
3526 
3527 		remove_ghost_note ();
3528 		show_verbose_cursor (ev->note ());
3529 	}
3530 }
3531 
3532 void
note_left(NoteBase *)3533 MidiRegionView::note_left (NoteBase*)
3534 {
3535 	_entered_note = 0;
3536 
3537 	for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3538 		(*i)->hide_velocity ();
3539 	}
3540 
3541 	hide_verbose_cursor ();
3542 }
3543 
3544 void
patch_entered(PatchChange * p)3545 MidiRegionView::patch_entered (PatchChange* p)
3546 {
3547 	ostringstream s;
3548 	s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3549 	  << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3550 	  << _("Channel ") << ((int) p->patch()->channel() + 1);
3551 	show_verbose_cursor (s.str(), 10, 20);
3552 	p->item().grab_focus();
3553 }
3554 
3555 void
patch_left(PatchChange *)3556 MidiRegionView::patch_left (PatchChange *)
3557 {
3558 	hide_verbose_cursor ();
3559 	/* focus will transfer back via the enter-notify event sent to this
3560 	 * midi region view.
3561 	 */
3562 }
3563 
3564 void
sysex_entered(SysEx * p)3565 MidiRegionView::sysex_entered (SysEx* p)
3566 {
3567 	// ostringstream s;
3568 	// CAIROCANVAS
3569 	// need a way to extract text from p->_flag->_text
3570 	// s << p->text();
3571 	// show_verbose_cursor (s.str(), 10, 20);
3572 	p->item().grab_focus();
3573 }
3574 
3575 void
sysex_left(SysEx *)3576 MidiRegionView::sysex_left (SysEx *)
3577 {
3578 	hide_verbose_cursor ();
3579 	/* focus will transfer back via the enter-notify event sent to this
3580 	 * midi region view.
3581 	 */
3582 }
3583 
3584 void
note_mouse_position(float x_fraction,float,bool can_set_cursor)3585 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3586 {
3587 	Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3588 	Editing::MouseMode mm = editor->current_mouse_mode();
3589 	bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3590 
3591 	Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3592 	if (can_set_cursor && ctx) {
3593 		if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3594 			ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3595 		} else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3596 			ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3597 		} else {
3598 			ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3599 		}
3600 	}
3601 }
3602 
3603 uint32_t
get_fill_color() const3604 MidiRegionView::get_fill_color() const
3605 {
3606 	const std::string mod_name = (_dragging ? "dragging region" :
3607 	                              trackview.editor().internal_editing() ? "editable region" :
3608 	                              "midi frame base");
3609 
3610 
3611 	if (_selected) {
3612 		return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3613 	}
3614 
3615 	if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3616 	    !UIConfiguration::instance().get_color_regions_using_track_color()) {
3617 		return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3618 	}
3619 
3620 	return UIConfiguration::instance().color_mod (fill_color, mod_name);
3621 }
3622 
3623 void
midi_channel_mode_changed()3624 MidiRegionView::midi_channel_mode_changed ()
3625 {
3626 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3627 	uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3628 	ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3629 
3630 	if (mode == ForceChannel) {
3631 		mask = 0xFFFF; // Show all notes as active (below)
3632 	}
3633 
3634 	// Update notes for selection
3635 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3636 		i->second->on_channel_selection_change (mask);
3637 	}
3638 
3639 	_patch_changes.clear ();
3640 	display_patch_changes ();
3641 }
3642 
3643 void
instrument_settings_changed()3644 MidiRegionView::instrument_settings_changed ()
3645 {
3646 	redisplay_model();
3647 
3648 	for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
3649 		(*x).second->update_name ();
3650 	}
3651 }
3652 
3653 void
cut_copy_clear(Editing::CutCopyOp op)3654 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3655 {
3656 	if (_selection.empty()) {
3657 		return;
3658 	}
3659 
3660 	PublicEditor& editor (trackview.editor());
3661 
3662 	switch (op) {
3663 	case Delete:
3664 		/* XXX what to do ? */
3665 		break;
3666 	case Cut:
3667 	case Copy:
3668 		editor.get_cut_buffer().add (selection_as_cut_buffer());
3669 		break;
3670 	default:
3671 		break;
3672 	}
3673 
3674 	if (op != Copy) {
3675 
3676 		start_note_diff_command();
3677 
3678 		for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3679 			switch (op) {
3680 			case Copy:
3681 				break;
3682 			case Delete:
3683 			case Cut:
3684 			case Clear:
3685 				note_diff_remove_note (*i);
3686 				break;
3687 			}
3688 		}
3689 
3690 		apply_diff();
3691 	}
3692 }
3693 
3694 MidiCutBuffer*
selection_as_cut_buffer() const3695 MidiRegionView::selection_as_cut_buffer () const
3696 {
3697 	Notes notes;
3698 
3699 	for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3700 		NoteType* n = (*i)->note().get();
3701 		notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3702 	}
3703 
3704 	MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3705 	cb->set (notes);
3706 
3707 	return cb;
3708 }
3709 
3710 /** This method handles undo */
3711 bool
paste(samplepos_t pos,const::Selection & selection,PasteContext & ctx,const int32_t sub_num)3712 MidiRegionView::paste (samplepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3713 {
3714 	bool commit = false;
3715 	// Paste notes, if available
3716 	MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3717 	if (m != selection.midi_notes.end()) {
3718 		ctx.counts.increase_n_notes();
3719 		if (!(*m)->empty()) {
3720 			commit = true;
3721 		}
3722 		paste_internal(pos, ctx.count, ctx.times, **m);
3723 	}
3724 
3725 	// Paste control points to automation children, if available
3726 	typedef RouteTimeAxisView::AutomationTracks ATracks;
3727 	const ATracks& atracks = midi_view()->automation_tracks();
3728 	for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3729 		if (a->second->paste(pos, selection, ctx, sub_num)) {
3730 			if(!commit) {
3731 				trackview.editor().begin_reversible_command (Operations::paste);
3732 			}
3733 			commit = true;
3734 		}
3735 	}
3736 
3737 	if (commit) {
3738 		trackview.editor().commit_reversible_command ();
3739 	}
3740 	return true;
3741 }
3742 
3743 /** This method handles undo */
3744 void
paste_internal(samplepos_t pos,unsigned paste_count,float times,const MidiCutBuffer & mcb)3745 MidiRegionView::paste_internal (samplepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3746 {
3747 	if (mcb.empty()) {
3748 		return;
3749 	}
3750 
3751 	start_note_diff_command (_("paste"));
3752 
3753 	const Temporal::Beats snap_beats    = get_grid_beats(pos);
3754 	const Temporal::Beats first_time    = (*mcb.notes().begin())->time();
3755 	const Temporal::Beats last_time     = (*mcb.notes().rbegin())->end_time();
3756 	const Temporal::Beats duration      = last_time - first_time;
3757 	const Temporal::Beats snap_duration = duration.snap_to(snap_beats);
3758 	const Temporal::Beats paste_offset  = snap_duration * paste_count;
3759 	const Temporal::Beats quarter_note  = absolute_samples_to_source_beats(pos) + paste_offset;
3760 	Temporal::Beats       end_point     = Temporal::Beats();
3761 
3762 	DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3763 	                                               first_time,
3764 	                                               last_time,
3765 	                                               duration, pos, _region->position(),
3766 	                                               quarter_note));
3767 
3768 	for (int n = 0; n < (int) times; ++n) {
3769 
3770 		for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3771 
3772 			boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3773 			copied_note->set_time (quarter_note + copied_note->time() - first_time);
3774 			copied_note->set_id (Evoral::next_event_id());
3775 
3776 			/* make all newly added notes selected */
3777 
3778 			note_diff_add_note (copied_note, true);
3779 			end_point = copied_note->end_time();
3780 		}
3781 	}
3782 
3783 	/* if we pasted past the current end of the region, extend the region */
3784 
3785 	samplepos_t end_sample = source_beats_to_absolute_samples (end_point);
3786 	samplepos_t region_end = _region->position() + _region->length() - 1;
3787 
3788 	if (end_sample > region_end) {
3789 
3790 		DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_sample));
3791 
3792 		_region->clear_changes ();
3793 		/* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3794 		_region->set_length (end_sample - _region->position(), trackview.editor().get_grid_music_divisions (0));
3795 		trackview.session()->add_command (new StatefulDiffCommand (_region));
3796 	}
3797 
3798 	apply_diff (true);
3799 }
3800 
3801 struct EventNoteTimeEarlyFirstComparator {
operator ()EventNoteTimeEarlyFirstComparator3802 	bool operator() (NoteBase* a, NoteBase* b) {
3803 		return a->note()->time() < b->note()->time();
3804 	}
3805 };
3806 
3807 void
goto_next_note(bool add_to_selection)3808 MidiRegionView::goto_next_note (bool add_to_selection)
3809 {
3810 	bool use_next = false;
3811 
3812 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3813 	uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3814 	NoteBase* first_note = 0;
3815 
3816 	MidiModel::ReadLock lock(_model->read_lock());
3817 	MidiModel::Notes& notes (_model->notes());
3818 
3819 	if (notes.empty()) {
3820 		return;
3821 	}
3822 
3823 	trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
3824 
3825 	for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
3826 		NoteBase* cne = 0;
3827 		if ((cne = find_canvas_note (*n))) {
3828 
3829 			if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
3830 				first_note = cne;
3831 			}
3832 
3833 			if (cne->selected()) {
3834 				use_next = true;
3835 				continue;
3836 			} else if (use_next) {
3837 				if (channel_mask & (1 << (*n)->channel())) {
3838 					if (!add_to_selection) {
3839 						unique_select (cne);
3840 					} else {
3841 						note_selected (cne, true, false);
3842 					}
3843 
3844 					return;
3845 				}
3846 			}
3847 		}
3848 	}
3849 
3850 	/* use the first one */
3851 
3852 	if (!_events.empty() && first_note) {
3853 		unique_select (first_note);
3854 	}
3855 
3856 
3857 	trackview.editor().commit_reversible_selection_op();
3858 }
3859 
3860 void
goto_previous_note(bool add_to_selection)3861 MidiRegionView::goto_previous_note (bool add_to_selection)
3862 {
3863 	bool use_next = false;
3864 
3865 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3866 	uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3867 	NoteBase* last_note = 0;
3868 
3869 	MidiModel::ReadLock lock(_model->read_lock());
3870 	MidiModel::Notes& notes (_model->notes());
3871 
3872 	if (notes.empty()) {
3873 		return;
3874 	}
3875 
3876 	trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
3877 
3878 	for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
3879 		NoteBase* cne = 0;
3880 		if ((cne = find_canvas_note (*n))) {
3881 
3882 			if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
3883 				last_note = cne;
3884 			}
3885 
3886 			if (cne->selected()) {
3887 				use_next = true;
3888 				continue;
3889 
3890 			} else if (use_next) {
3891 				if (channel_mask & (1 << (*n)->channel())) {
3892 					if (!add_to_selection) {
3893 						unique_select (cne);
3894 					} else {
3895 						note_selected (cne, true, false);
3896 					}
3897 
3898 					return;
3899 				}
3900 			}
3901 		}
3902 	}
3903 
3904 	/* use the last one */
3905 
3906 	if (!_events.empty() && last_note) {
3907 		unique_select (last_note);
3908 	}
3909 
3910 	trackview.editor().commit_reversible_selection_op();
3911 }
3912 
3913 void
selection_as_notelist(Notes & selected,bool allow_all_if_none_selected)3914 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3915 {
3916 	bool had_selected = false;
3917 
3918 	/* we previously time sorted events here, but Notes is a multiset sorted by time */
3919 
3920 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3921 		if (i->second->selected()) {
3922 			selected.insert (i->first);
3923 			had_selected = true;
3924 		}
3925 	}
3926 
3927 	if (allow_all_if_none_selected && !had_selected) {
3928 		for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3929 			selected.insert (i->first);
3930 		}
3931 	}
3932 }
3933 
3934 void
update_ghost_note(double x,double y,uint32_t state)3935 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3936 {
3937 	assert (_ghost_note);
3938 	x = std::max(0.0, x);
3939 
3940 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3941 
3942 	_last_ghost_x = x;
3943 	_last_ghost_y = y;
3944 
3945 	_note_group->canvas_to_item (x, y);
3946 
3947 	PublicEditor& editor = trackview.editor ();
3948 
3949 	samplepos_t const unsnapped_sample = editor.pixel_to_sample (x);
3950 
3951 	const int32_t divisions = editor.get_grid_music_divisions (state);
3952 	const bool shift_snap = midi_view()->note_mode() != Percussive;
3953 	const Temporal::Beats snapped_beats = snap_sample_to_grid_underneath (unsnapped_sample, divisions, shift_snap);
3954 
3955 	/* prevent Percussive mode from displaying a ghost hit at region end */
3956 	if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3957 		_ghost_note->hide();
3958 		hide_verbose_cursor ();
3959 		return;
3960 	}
3961 
3962 	/* ghost note may have been snapped before region */
3963 	if (snapped_beats.to_double() < 0.0) {
3964 		_ghost_note->hide();
3965 		return;
3966 	}
3967 
3968 	_ghost_note->show();
3969 
3970 	/* calculate time in beats relative to start of source */
3971 	const Temporal::Beats length = get_grid_beats(unsnapped_sample + _region->position());
3972 
3973 	_ghost_note->note()->set_time (snapped_beats);
3974 	_ghost_note->note()->set_length (length);
3975 	_ghost_note->note()->set_note (y_to_note (y));
3976 	_ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3977 	_ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3978 	/* the ghost note does not appear in ghost regions, so pass false in here */
3979 	update_note (_ghost_note, false);
3980 
3981 	show_verbose_cursor (_ghost_note->note ());
3982 }
3983 
3984 void
create_ghost_note(double x,double y,uint32_t state)3985 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3986 {
3987 	remove_ghost_note ();
3988 
3989 	boost::shared_ptr<NoteType> g (new NoteType);
3990 	if (midi_view()->note_mode() == Sustained) {
3991 		_ghost_note = new Note (*this, _note_group, g);
3992 	} else {
3993 		_ghost_note = new Hit (*this, _note_group, 10, g);
3994 	}
3995 	_ghost_note->set_ignore_events (true);
3996 	_ghost_note->set_outline_color (0x000000aa);
3997 	update_ghost_note (x, y, state);
3998 	_ghost_note->show ();
3999 
4000 	show_verbose_cursor (_ghost_note->note ());
4001 }
4002 
4003 void
remove_ghost_note()4004 MidiRegionView::remove_ghost_note ()
4005 {
4006 	delete _ghost_note;
4007 	_ghost_note = 0;
4008 }
4009 
4010 void
hide_verbose_cursor()4011 MidiRegionView::hide_verbose_cursor ()
4012 {
4013 	trackview.editor().verbose_cursor()->hide ();
4014 	MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4015 	if (mtv) {
4016 		mtv->set_note_highlight (NO_MIDI_NOTE);
4017 	}
4018 }
4019 
4020 void
snap_changed()4021 MidiRegionView::snap_changed ()
4022 {
4023 	if (!_ghost_note) {
4024 		return;
4025 	}
4026 
4027 	create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
4028 }
4029 
4030 void
drop_down_keys()4031 MidiRegionView::drop_down_keys ()
4032 {
4033 	_mouse_state = None;
4034 }
4035 
4036 void
maybe_select_by_position(GdkEventButton * ev,double,double y)4037 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
4038 {
4039 	/* XXX: This is dead code.  What was it for? */
4040 
4041 	double note = y_to_note(y);
4042 	Events e;
4043 	MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4044 
4045 	uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
4046 
4047 	if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
4048 		get_events (e, Evoral::Sequence<Temporal::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
4049 	} else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
4050 		get_events (e, Evoral::Sequence<Temporal::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
4051 	} else {
4052 		return;
4053 	}
4054 
4055 	bool add_mrv_selection = false;
4056 
4057 	if (_selection.empty()) {
4058 		add_mrv_selection = true;
4059 	}
4060 
4061 	for (Events::iterator i = e.begin(); i != e.end(); ++i) {
4062 		if (_selection.insert (i->second).second) {
4063 			i->second->set_selected (true);
4064 		}
4065 	}
4066 
4067 	if (add_mrv_selection) {
4068 		PublicEditor& editor (trackview.editor());
4069 		editor.get_selection().add (this);
4070 	}
4071 }
4072 
4073 void
color_handler()4074 MidiRegionView::color_handler ()
4075 {
4076 	RegionView::color_handler ();
4077 
4078 	_patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
4079 	_patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
4080 
4081 	for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
4082 		i->second->set_selected (i->second->selected()); // will change color
4083 	}
4084 
4085 	/* XXX probably more to do here */
4086 }
4087 
4088 void
enable_display(bool yn)4089 MidiRegionView::enable_display (bool yn)
4090 {
4091 	RegionView::enable_display (yn);
4092 }
4093 
4094 void
show_step_edit_cursor(Temporal::Beats pos)4095 MidiRegionView::show_step_edit_cursor (Temporal::Beats pos)
4096 {
4097 	if (_step_edit_cursor == 0) {
4098 		ArdourCanvas::Item* const group = get_canvas_group();
4099 
4100 		_step_edit_cursor = new ArdourCanvas::Rectangle (group);
4101 		_step_edit_cursor->set_y0 (0);
4102 		_step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
4103 		_step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
4104 		_step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
4105 	}
4106 
4107 	move_step_edit_cursor (pos);
4108 	_step_edit_cursor->show ();
4109 }
4110 
4111 void
move_step_edit_cursor(Temporal::Beats pos)4112 MidiRegionView::move_step_edit_cursor (Temporal::Beats pos)
4113 {
4114 	_step_edit_cursor_position = pos;
4115 
4116 	if (_step_edit_cursor) {
4117 		double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_samples (pos));
4118 		_step_edit_cursor->set_x0 (pixel);
4119 		set_step_edit_cursor_width (_step_edit_cursor_width);
4120 	}
4121 }
4122 
4123 void
hide_step_edit_cursor()4124 MidiRegionView::hide_step_edit_cursor ()
4125 {
4126 	if (_step_edit_cursor) {
4127 		_step_edit_cursor->hide ();
4128 	}
4129 }
4130 
4131 void
set_step_edit_cursor_width(Temporal::Beats beats)4132 MidiRegionView::set_step_edit_cursor_width (Temporal::Beats beats)
4133 {
4134 	_step_edit_cursor_width = beats;
4135 
4136 	if (_step_edit_cursor) {
4137 		_step_edit_cursor->set_x1 (_step_edit_cursor->x0()
4138 		                           + trackview.editor().sample_to_pixel (
4139 		                             region_beats_to_region_samples (_step_edit_cursor_position + beats)
4140 		                             - region_beats_to_region_samples (_step_edit_cursor_position)));
4141 	}
4142 }
4143 
4144 /** Called when a diskstream on our track has received some data.  Update the view, if applicable.
4145  *  @param w Source that the data will end up in.
4146  */
4147 void
data_recorded(boost::weak_ptr<MidiSource> w)4148 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
4149 {
4150 	if (!_active_notes) {
4151 		/* we aren't actively being recorded to */
4152 		return;
4153 	}
4154 
4155 	boost::shared_ptr<MidiSource> src = w.lock ();
4156 	if (!src || src != midi_region()->midi_source()) {
4157 		/* recorded data was not destined for our source */
4158 		return;
4159 	}
4160 
4161 	MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
4162 
4163 	boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
4164 
4165 	samplepos_t back = max_samplepos;
4166 
4167 	for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4168 		const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4169 
4170 		if (ev.is_channel_event()) {
4171 			if (get_channel_mode() == FilterChannels) {
4172 				if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4173 					continue;
4174 				}
4175 			}
4176 		}
4177 
4178 		/* convert from session samples to source beats */
4179 		Temporal::Beats const time_beats = _source_relative_time_converter.from(
4180 			ev.time() - src->natural_position() + _region->start());
4181 
4182 		if (ev.type() == MIDI_CMD_NOTE_ON) {
4183 
4184 			boost::shared_ptr<NoteType> note (new NoteType (ev.channel(), time_beats, std::numeric_limits<Temporal::Beats>::max() - time_beats, ev.note(), ev.velocity()));
4185 
4186 			assert (note->end_time() == std::numeric_limits<Temporal::Beats>::max());
4187 
4188 			add_note (note, true);
4189 
4190 			/* fix up our note range */
4191 			if (ev.note() < _current_range_min) {
4192 				midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4193 			} else if (ev.note() > _current_range_max) {
4194 				midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4195 			}
4196 
4197 		} else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4198 			resolve_note (ev.note (), time_beats);
4199 		}
4200 
4201 		back = ev.time ();
4202 	}
4203 
4204 	midi_stream_view()->check_record_layers (region(), back);
4205 }
4206 
4207 void
trim_front_starting()4208 MidiRegionView::trim_front_starting ()
4209 {
4210 	/* We used to eparent the note group to the region view's parent, so that it didn't change.
4211 	   now we update it.
4212 	*/
4213 }
4214 
4215 void
trim_front_ending()4216 MidiRegionView::trim_front_ending ()
4217 {
4218 	if (_region->start() < 0) {
4219 		/* Trim drag made start time -ve; fix this */
4220 		midi_region()->fix_negative_start ();
4221 	}
4222 }
4223 
4224 void
edit_patch_change(PatchChange * pc)4225 MidiRegionView::edit_patch_change (PatchChange* pc)
4226 {
4227 	PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4228 
4229 	int response = d.run();
4230 
4231 	switch (response) {
4232 	case Gtk::RESPONSE_ACCEPT:
4233 		break;
4234 	case Gtk::RESPONSE_REJECT:
4235 		delete_patch_change (pc);
4236 		return;
4237 	default:
4238 		return;
4239 	}
4240 
4241 	change_patch_change (pc->patch(), d.patch ());
4242 }
4243 
4244 void
delete_sysex(SysEx *)4245 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4246 {
4247 	// CAIROCANVAS
4248 	// sysyex object doesn't have a pointer to a sysex event
4249 	// MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4250 	// c->remove (sysex->sysex());
4251 	// _model->apply_command (*trackview.session(), c);
4252 
4253 	//_sys_exes.clear ();
4254 	// display_sysexes();
4255 }
4256 
4257 std::string
get_note_name(boost::shared_ptr<NoteType> n,uint8_t note_value) const4258 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4259 {
4260 	using namespace MIDI::Name;
4261 	std::string name;
4262 
4263 	MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4264 	if (mtv) {
4265 		MIDI::Name::PatchPrimaryKey patch_key;
4266 		get_patch_key_at (n->time(), n->channel(), patch_key);
4267 		name = instrument_info ().get_note_name (patch_key.bank(), patch_key.program(), n->channel(), note_value);
4268 	}
4269 
4270 	char buf[128];
4271 	snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4272 	          (int) note_value,
4273 	          name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4274 	          (int) n->channel() + 1,
4275 	          (int) n->velocity());
4276 
4277 	return buf;
4278 }
4279 
4280 void
show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,uint8_t new_value) const4281 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4282                                                        uint8_t new_value) const
4283 {
4284 	MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4285 	if (mtv) {
4286 		mtv->set_note_highlight (new_value);
4287 	}
4288 
4289 	show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4290 }
4291 
4292 void
show_verbose_cursor(boost::shared_ptr<NoteType> n) const4293 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4294 {
4295 	show_verbose_cursor_for_new_note_value(n, n->note());
4296 }
4297 
4298 void
show_verbose_cursor(string const & text,double xoffset,double yoffset) const4299 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4300 {
4301 	trackview.editor().verbose_cursor()->set (text);
4302 	trackview.editor().verbose_cursor()->show ();
4303 	trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4304 }
4305 
4306 uint8_t
get_velocity_for_add(MidiModel::TimeType time) const4307 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4308 {
4309 	if (_model->notes().empty()) {
4310 		return 0x40;  // No notes, use default
4311 	}
4312 
4313 	MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4314 	if (m == _model->notes().begin()) {
4315 		// Before the start, use the velocity of the first note
4316 		return (*m)->velocity();
4317 	} else if (m == _model->notes().end()) {
4318 		// Past the end, use the velocity of the last note
4319 		--m;
4320 		return (*m)->velocity();
4321 	}
4322 
4323 	// Interpolate velocity of surrounding notes
4324 	MidiModel::Notes::const_iterator n = m;
4325 	--n;
4326 
4327 	const double frac = ((time - (*n)->time()).to_double() /
4328 	                     ((*m)->time() - (*n)->time()).to_double());
4329 
4330 	return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4331 }
4332 
4333 /** @param p A session samplepos.
4334  *  @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4335  *  bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4336  *  @return beat duration of p snapped to the grid subdivision underneath it.
4337  */
4338 Temporal::Beats
snap_sample_to_grid_underneath(samplepos_t p,int32_t divisions,bool shift_snap) const4339 MidiRegionView::snap_sample_to_grid_underneath (samplepos_t p, int32_t divisions, bool shift_snap) const
4340 {
4341 	TempoMap& map (trackview.session()->tempo_map());
4342 	double eqaf = map.exact_qn_at_sample (p + _region->position(), divisions);
4343 
4344 	if (divisions != 0 && shift_snap) {
4345 		const double qaf = map.quarter_note_at_sample (p + _region->position());
4346 		/* Hack so that we always snap to the note that we are over, instead of snapping
4347 		   to the next one if we're more than halfway through the one we're over.
4348 		*/
4349 		const Temporal::Beats grid_beats = get_grid_beats (p + _region->position());
4350 		const double rem = eqaf - qaf;
4351 		if (rem >= 0.0) {
4352 			eqaf -= grid_beats.to_double();
4353 		}
4354 	}
4355 	const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4356 
4357 	return Temporal::Beats (eqaf - session_start_off);
4358 }
4359 
4360 ChannelMode
get_channel_mode() const4361 MidiRegionView::get_channel_mode () const
4362 {
4363 	RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4364 	return rtav->midi_track()->get_playback_channel_mode();
4365 }
4366 
4367 uint16_t
get_selected_channels() const4368 MidiRegionView::get_selected_channels () const
4369 {
4370 	RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4371 	return rtav->midi_track()->get_playback_channel_mask();
4372 }
4373 
4374 
4375 Temporal::Beats
get_grid_beats(samplepos_t pos) const4376 MidiRegionView::get_grid_beats(samplepos_t pos) const
4377 {
4378 	PublicEditor& editor  = trackview.editor();
4379 	bool          success = false;
4380 	Temporal::Beats beats   = editor.get_grid_type_as_beats (success, pos);
4381 	if (!success) {
4382 		beats = Temporal::Beats(1);
4383 	}
4384 	return beats;
4385 }
4386 uint8_t
y_to_note(double y) const4387 MidiRegionView::y_to_note (double y) const
4388 {
4389 	int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4390 		+ _current_range_min;
4391 
4392 	if (n < 0) {
4393 		return 0;
4394 	} else if (n > 127) {
4395 		return 127;
4396 	}
4397 
4398 	/* min due to rounding and/or off-by-one errors */
4399 	return min ((uint8_t) n, _current_range_max);
4400 }
4401 
4402 double
note_to_y(uint8_t note) const4403 MidiRegionView::note_to_y(uint8_t note) const
4404 {
4405 	return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
4406 }
4407 
4408 double
session_relative_qn(double qn) const4409 MidiRegionView::session_relative_qn (double qn) const
4410 {
4411 	return qn + (region()->quarter_note() - midi_region()->start_beats());
4412 }
4413