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