ardour
midi_region_view.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2001-2011 Paul Davis
3  Author: David Robillard
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 
20 #include <cmath>
21 #include <algorithm>
22 #include <ostream>
23 
24 #include <gtkmm.h>
25 
26 #include "gtkmm2ext/gtk_ui.h"
27 
28 #include <sigc++/signal.h>
29 
30 #include "midi++/midnam_patch.h"
31 
32 #include "pbd/memento_command.h"
34 
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
42 
43 #include "evoral/Parameter.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
47 
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
50 
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
54 #include "debug.h"
55 #include "editor.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
60 #include "keyboard.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
79 #include "ardour_ui.h"
80 #include "note.h"
81 #include "hit.h"
82 #include "patch_change.h"
83 #include "sys_ex.h"
84 
85 #include "i18n.h"
86 
87 using namespace ARDOUR;
88 using namespace PBD;
89 using namespace Editing;
90 using namespace std;
92 
93 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
94 
95 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
96 
97 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
100  double spu,
101  uint32_t basic_color)
102  : RegionView (parent, tv, r, spu, basic_color)
103  , _current_range_min(0)
104  , _current_range_max(0)
105  , _region_relative_time_converter(r->session().tempo_map(), r->position())
106  , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
107  , _active_notes(0)
108  , _note_group (new ArdourCanvas::Container (group))
109  , _note_diff_command (0)
110  , _ghost_note(0)
111  , _step_edit_cursor (0)
112  , _step_edit_cursor_width (1.0)
113  , _step_edit_cursor_position (0.0)
114  , _channel_selection_scoped_note (0)
115  , _temporary_note_group (0)
116  , _mouse_state(None)
117  , _pressed_button(0)
118  , _sort_needed (true)
119  , _optimization_iterator (_events.end())
120  , _list_editor (0)
121  , _no_sound_notes (false)
122  , _last_display_zoom (0)
123  , _last_event_x (0)
124  , _last_event_y (0)
125  , _grabbed_keyboard (false)
126  , _entered (false)
127  , _mouse_changed_selection (false)
128 {
129  CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
130  _note_group->raise_to_top();
131  PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
132 
133  Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
135 
137 
139  editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
140 }
141 
142 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
143  RouteTimeAxisView& tv,
145  double spu,
146  uint32_t basic_color,
147  bool recording,
148  TimeAxisViewItem::Visibility visibility)
149  : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
150  , _current_range_min(0)
151  , _current_range_max(0)
152  , _region_relative_time_converter(r->session().tempo_map(), r->position())
153  , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
154  , _active_notes(0)
155  , _note_group (new ArdourCanvas::Container (group))
156  , _note_diff_command (0)
157  , _ghost_note(0)
158  , _step_edit_cursor (0)
159  , _step_edit_cursor_width (1.0)
160  , _step_edit_cursor_position (0.0)
161  , _channel_selection_scoped_note (0)
162  , _temporary_note_group (0)
163  , _mouse_state(None)
164  , _pressed_button(0)
165  , _sort_needed (true)
166  , _optimization_iterator (_events.end())
167  , _list_editor (0)
168  , _no_sound_notes (false)
169  , _last_display_zoom (0)
170  , _last_event_x (0)
171  , _last_event_y (0)
172  , _grabbed_keyboard (false)
173  , _entered (false)
174  , _mouse_changed_selection (false)
175 {
176  CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
177  _note_group->raise_to_top();
178 
179  PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
180 
182 
184 
186  editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
187 }
188 
189 void
190 MidiRegionView::parameter_changed (std::string const & p)
191 {
192  if (p == "display-first-midi-bank-as-zero") {
193  if (_enable_display) {
194  redisplay_model();
195  }
196  }
197 }
198 
200  : sigc::trackable(other)
201  , RegionView (other)
202  , _current_range_min(0)
203  , _current_range_max(0)
204  , _region_relative_time_converter(other.region_relative_time_converter())
205  , _source_relative_time_converter(other.source_relative_time_converter())
206  , _active_notes(0)
207  , _note_group (new ArdourCanvas::Container (get_canvas_group()))
208  , _note_diff_command (0)
209  , _ghost_note(0)
210  , _step_edit_cursor (0)
211  , _step_edit_cursor_width (1.0)
212  , _step_edit_cursor_position (0.0)
213  , _channel_selection_scoped_note (0)
214  , _temporary_note_group (0)
215  , _mouse_state(None)
216  , _pressed_button(0)
217  , _sort_needed (true)
218  , _optimization_iterator (_events.end())
219  , _list_editor (0)
220  , _no_sound_notes (false)
221  , _last_display_zoom (0)
222  , _last_event_x (0)
223  , _last_event_y (0)
224  , _grabbed_keyboard (false)
225  , _entered (false)
226  , _mouse_changed_selection (false)
227 {
228  init (false);
229 }
230 
232  : RegionView (other, boost::shared_ptr<Region> (region))
233  , _current_range_min(0)
234  , _current_range_max(0)
235  , _region_relative_time_converter(other.region_relative_time_converter())
236  , _source_relative_time_converter(other.source_relative_time_converter())
237  , _active_notes(0)
238  , _note_group (new ArdourCanvas::Container (get_canvas_group()))
239  , _note_diff_command (0)
240  , _ghost_note(0)
241  , _step_edit_cursor (0)
242  , _step_edit_cursor_width (1.0)
243  , _step_edit_cursor_position (0.0)
244  , _channel_selection_scoped_note (0)
245  , _temporary_note_group (0)
246  , _mouse_state(None)
247  , _pressed_button(0)
248  , _sort_needed (true)
249  , _optimization_iterator (_events.end())
250  , _list_editor (0)
251  , _no_sound_notes (false)
252  , _last_display_zoom (0)
253  , _last_event_x (0)
254  , _last_event_y (0)
255  , _grabbed_keyboard (false)
256  , _entered (false)
257  , _mouse_changed_selection (false)
258 {
259  init (true);
260 }
261 
262 void
264 {
265  PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
266 
269  gui_context());
270 
271  if (wfd) {
272  Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
273  midi_region()->midi_source(0)->load_model(lm);
274  }
275 
276  _model = midi_region()->midi_source(0)->model();
277  _enable_display = false;
278  fill_color_name = "midi frame base";
279 
280  RegionView::init (false);
281 
283 
284  region_muted ();
287  region_locked ();
288 
289  set_colors ();
290 
291  _enable_display = true;
292  if (_model) {
293  if (wfd) {
295  }
296  }
297 
299 
300  group->raise_to_top();
301 
303  boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
304  gui_context ());
305 
308 
310  boost::bind (&MidiRegionView::snap_changed, this),
311  gui_context());
312 
314  boost::bind (&MidiRegionView::mouse_mode_changed, this),
315  gui_context ());
316 
317  Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
319 
321 
323  editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
324 }
325 
328 {
329  RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
330  return route_ui->route()->instrument_info();
331 }
332 
335 {
337 }
338 
339 void
341 {
342  midi_view()->midi_track()->DataRecorded.connect(
343  *this, invalidator(*this),
344  boost::bind (&MidiRegionView::data_recorded, this, _1),
345  gui_context());
346 }
347 
348 bool
350 {
351  if (in_destructor || _recregion) {
352  return false;
353  }
354 
355  if (!trackview.editor().internal_editing()) {
356  // not in internal edit mode, so just act like a normal region
357  return RegionView::canvas_group_event (ev);
358  }
359 
360  bool r;
361 
362  switch (ev->type) {
363  case GDK_ENTER_NOTIFY:
364  _last_event_x = ev->crossing.x;
365  _last_event_y = ev->crossing.y;
366  enter_notify(&ev->crossing);
367  // set entered_regionview (among other things)
368  return RegionView::canvas_group_event (ev);
369 
370  case GDK_LEAVE_NOTIFY:
371  _last_event_x = ev->crossing.x;
372  _last_event_y = ev->crossing.y;
373  leave_notify(&ev->crossing);
374  // reset entered_regionview (among other things)
375  return RegionView::canvas_group_event (ev);
376 
377  case GDK_SCROLL:
378  if (scroll (&ev->scroll)) {
379  return true;
380  }
381  break;
382 
383  case GDK_KEY_PRESS:
384  return key_press (&ev->key);
385 
386  case GDK_KEY_RELEASE:
387  return key_release (&ev->key);
388 
389  case GDK_BUTTON_PRESS:
390  return button_press (&ev->button);
391 
392  case GDK_BUTTON_RELEASE:
393  r = button_release (&ev->button);
394  return r;
395 
396  case GDK_MOTION_NOTIFY:
397  _last_event_x = ev->motion.x;
398  _last_event_y = ev->motion.y;
399  return motion (&ev->motion);
400 
401  default:
402  break;
403  }
404 
405  return RegionView::canvas_group_event (ev);
406 }
407 
408 bool
409 MidiRegionView::enter_notify (GdkEventCrossing* ev)
410 {
411  enter_internal();
412 
413  _entered = true;
414  return false;
415 }
416 
417 bool
418 MidiRegionView::leave_notify (GdkEventCrossing*)
419 {
420  leave_internal();
421 
422  _entered = false;
423  return false;
424 }
425 
426 void
428 {
429  // Adjust frame colour (become more transparent for internal tools)
430  set_frame_color();
431 
432  if (_entered) {
434  // Switched in to internal editing mode while entered
435  enter_internal();
436  } else {
437  // Switched out of internal editing mode while entered
438  leave_internal();
439  }
440  }
441 }
442 
443 void
445 {
446  if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
447  // Show ghost note under pencil
449  }
450 
451  if (!_selection.empty()) {
452  // Grab keyboard for moving selected notes with arrow keys
453  Keyboard::magic_widget_grab_focus();
454  _grabbed_keyboard = true;
455  }
456 
457  // Lower frame handles below notes so they don't steal events
458  if (frame_handle_start) {
459  frame_handle_start->lower_to_bottom();
460  }
461  if (frame_handle_end) {
462  frame_handle_end->lower_to_bottom();
463  }
464 }
465 
466 void
468 {
471 
472  if (_grabbed_keyboard) {
473  Keyboard::magic_widget_drop_focus();
474  _grabbed_keyboard = false;
475  }
476 
477  // Raise frame handles above notes so they catch events
478  if (frame_handle_start) {
479  frame_handle_start->raise_to_top();
480  }
481  if (frame_handle_end) {
482  frame_handle_end->raise_to_top();
483  }
484 }
485 
486 bool
487 MidiRegionView::button_press (GdkEventButton* ev)
488 {
489  if (ev->button != 1) {
490  return false;
491  }
492 
493  Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
494  MouseMode m = editor->current_mouse_mode();
495 
496  if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
498  }
499 
501 
502  _pressed_button = ev->button;
504 
505  return true;
506  }
507 
508  _pressed_button = ev->button;
509  _mouse_changed_selection = false;
510 
511  return true;
512 }
513 
514 bool
515 MidiRegionView::button_release (GdkEventButton* ev)
516 {
517  double event_x, event_y;
518 
519  if (ev->button != 1) {
520  return false;
521  }
522 
523  event_x = ev->x;
524  event_y = ev->y;
525 
526  group->canvas_to_item (event_x, event_y);
527  group->ungrab ();
528 
530 
532 
533  switch (_mouse_state) {
534  case Pressed: // Clicked
535 
536  switch (editor.current_mouse_mode()) {
537  case MouseRange:
538  /* no motion occured - simple click */
539  clear_selection ();
541  break;
542 
543  case MouseContent:
544  case MouseTimeFX:
545  {
546  clear_selection();
548 
549  if (Keyboard::is_insert_note_event(ev)) {
550 
551  double event_x, event_y;
552 
553  event_x = ev->x;
554  event_y = ev->y;
555  group->canvas_to_item (event_x, event_y);
556 
557  Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
558 
559  /* Shorten the length by 1 tick so that we can add a new note at the next
560  grid snap without it overlapping this one.
561  */
562  beats -= Evoral::Beats::tick();
563 
564  create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
565  }
566 
567  break;
568  }
569  case MouseDraw:
570  {
571  Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
572 
573  /* Shorten the length by 1 tick so that we can add a new note at the next
574  grid snap without it overlapping this one.
575  */
576  beats -= Evoral::Beats::tick();
577 
578  create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
579 
580  break;
581  }
582  default:
583  break;
584  }
585 
586  _mouse_state = None;
587  break;
588 
589  case SelectRectDragging:
590  case AddDragging:
591  editor.drags()->end_grab ((GdkEvent *) ev);
592  _mouse_state = None;
593  create_ghost_note (ev->x, ev->y);
594  break;
595 
596 
597  default:
598  break;
599  }
600 
602  trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
604  }
605 
606  return false;
607 }
608 
609 bool
610 MidiRegionView::motion (GdkEventMotion* ev)
611 {
613 
614  if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
615  Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
617 
618  create_ghost_note (ev->x, ev->y);
619 
620  } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
621  Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
622 
623  update_ghost_note (ev->x, ev->y);
624 
625  } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
626 
628  editor.verbose_cursor()->hide ();
629 
630  } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
631 
632  update_ghost_note (ev->x, ev->y);
633  }
634 
635  /* any motion immediately hides velocity text that may have been visible */
636 
637  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
638  (*i)->hide_velocity ();
639  }
640 
641  switch (_mouse_state) {
642  case Pressed:
643 
644  if (_pressed_button == 1) {
645 
646  MouseMode m = editor.current_mouse_mode();
647 
648  if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
649  editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
652  editor.verbose_cursor()->hide ();
653  return true;
654  } else if (m == MouseContent) {
655  editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
656  if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
657  clear_selection ();
659  }
661  return true;
662  } else if (m == MouseRange) {
663  editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
665  return true;
666  }
667  }
668 
669  return false;
670 
671  case SelectRectDragging:
673  case AddDragging:
674  editor.drags()->motion_handler ((GdkEvent *) ev, false);
675  break;
676 
677  case SelectTouchDragging:
678  return false;
679 
680  default:
681  break;
682 
683  }
684 
685  /* we may be dragging some non-note object (eg. patch-change, sysex)
686  */
687 
688  return editor.drags()->motion_handler ((GdkEvent *) ev, false);
689 }
690 
691 
692 bool
693 MidiRegionView::scroll (GdkEventScroll* ev)
694 {
695  if (_selection.empty()) {
696  return false;
697  }
698 
699  if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
700  /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
701  it still works for zoom.
702  */
703  return false;
704  }
705 
707 
708  bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
709  bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
710 
711  if (ev->direction == GDK_SCROLL_UP) {
712  change_velocities (true, fine, false, together);
713  } else if (ev->direction == GDK_SCROLL_DOWN) {
714  change_velocities (false, fine, false, together);
715  } else {
716  /* left, right: we don't use them */
717  return false;
718  }
719 
720  return true;
721 }
722 
723 bool
724 MidiRegionView::key_press (GdkEventKey* ev)
725 {
726  /* since GTK bindings are generally activated on press, and since
727  detectable auto-repeat is the name of the game and only sends
728  repeated presses, carry out key actions at key press, not release.
729  */
730 
731  bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
732 
733  if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
735  return true;
736 
737  } else if (ev->keyval == GDK_Escape && unmodified) {
738  clear_selection();
739  _mouse_state = None;
740 
741  } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
742 
743  bool start = (ev->keyval == GDK_comma);
744  bool end = (ev->keyval == GDK_period);
745  bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
746  bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
747 
748  change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
749 
750  return true;
751 
752  } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
753 
754  if (_selection.empty()) {
755  return false;
756  }
757 
759  return true;
760 
761  } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
762 
763  trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
764 
765  if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
766  goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
767  } else {
768  goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
769  }
770 
772 
773  return true;
774 
775  } else if (ev->keyval == GDK_Up) {
776 
777  bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
778  bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
779  bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
780 
781  if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
782  change_velocities (true, fine, allow_smush, together);
783  } else {
784  transpose (true, fine, allow_smush);
785  }
786  return true;
787 
788  } else if (ev->keyval == GDK_Down) {
789 
790  bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
791  bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
792  bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
793 
794  if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
795  change_velocities (false, fine, allow_smush, together);
796  } else {
797  transpose (false, fine, allow_smush);
798  }
799  return true;
800 
801  } else if (ev->keyval == GDK_Left) {
802 
803  bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
804  nudge_notes (false, fine);
805  return true;
806 
807  } else if (ev->keyval == GDK_Right) {
808 
809  bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
810  nudge_notes (true, fine);
811  return true;
812 
813  } else if (ev->keyval == GDK_c && unmodified) {
814  channel_edit ();
815  return true;
816 
817  } else if (ev->keyval == GDK_v && unmodified) {
818  velocity_edit ();
819  return true;
820  }
821 
822  return false;
823 }
824 
825 bool
827 {
828  if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
829  _mouse_state = None;
830  return true;
831  }
832  return false;
833 }
834 
835 void
837 {
838  if (_selection.empty()) {
839  return;
840  }
841 
842  /* pick a note somewhat at random (since Selection is a set<>) to
843  * provide the "current" channel for the dialog.
844  */
845 
846  uint8_t current_channel = (*_selection.begin())->note()->channel ();
847  MidiChannelDialog channel_dialog (current_channel);
848  int ret = channel_dialog.run ();
849 
850  switch (ret) {
851  case Gtk::RESPONSE_OK:
852  break;
853  default:
854  return;
855  }
856 
857  uint8_t new_channel = channel_dialog.active_channel ();
858 
859  start_note_diff_command (_("channel edit"));
860 
861  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
862  Selection::iterator next = i;
863  ++next;
864  change_note_channel (*i, new_channel);
865  i = next;
866  }
867 
868  apply_diff ();
869 }
870 
871 void
873 {
874  if (_selection.empty()) {
875  return;
876  }
877 
878  /* pick a note somewhat at random (since Selection is a set<>) to
879  * provide the "current" velocity for the dialog.
880  */
881 
882  uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
883  MidiVelocityDialog velocity_dialog (current_velocity);
884  int ret = velocity_dialog.run ();
885 
886  switch (ret) {
887  case Gtk::RESPONSE_OK:
888  break;
889  default:
890  return;
891  }
892 
893  uint8_t new_velocity = velocity_dialog.velocity ();
894 
895  start_note_diff_command (_("velocity edit"));
896 
897  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
898  Selection::iterator next = i;
899  ++next;
900  change_note_velocity (*i, new_velocity, false);
901  i = next;
902  }
903 
904  apply_diff ();
905 }
906 
907 void
909 {
910  if (!_list_editor) {
912  }
913  _list_editor->present ();
914 }
915 
922 void
924 {
925  if (length < 2 * DBL_EPSILON) {
926  return;
927  }
928 
929  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
930  MidiStreamView* const view = mtv->midi_view();
931 
932  // Start of note in frames relative to region start
933  if (snap_t) {
934  framecnt_t grid_frames;
935  t = snap_frame_to_grid_underneath (t, grid_frames);
936  }
937 
939  t + _region->start());
940 
941  const double note = view->y_to_note(y);
942  const uint8_t chan = mtv->get_channel_for_add();
943  const uint8_t velocity = get_velocity_for_add(beat_time);
944 
945  const boost::shared_ptr<NoteType> new_note(
946  new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
947 
948  if (_model->contains (new_note)) {
949  return;
950  }
951 
952  view->update_note_range(new_note->note());
953 
954  start_note_diff_command(_("add note"));
955 
956  clear_selection ();
957  note_diff_add_note (new_note, true, false);
958 
959  apply_diff();
960 
961  play_midi_note (new_note);
962 }
963 
964 void
965 MidiRegionView::clear_events (bool with_selection_signal)
966 {
967  clear_selection (with_selection_signal);
968 
969  MidiGhostRegion* gr;
970  for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
971  if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
972  gr->clear_events();
973  }
974  }
975 
976  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
977  delete *i;
978  }
979 
980  _events.clear();
981  _patch_changes.clear();
982  _sys_exes.clear();
984 }
985 
986 void
988 {
989  _model = model;
990 
993  /* Don't signal as nobody else needs to know until selection has been altered. */
994  clear_events (false);
995 
996  if (_enable_display) {
997  redisplay_model();
998  }
999 }
1000 
1001 void
1003 {
1004  if (!_note_diff_command) {
1007  }
1008 }
1009 
1010 void
1011 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1012 {
1013  if (_note_diff_command) {
1014  _note_diff_command->add (note);
1015  }
1016  if (selected) {
1017  _marked_for_selection.insert(note);
1018  }
1019  if (show_velocity) {
1020  _marked_for_velocity.insert(note);
1021  }
1022 }
1023 
1024 void
1026 {
1027  if (_note_diff_command && ev->note()) {
1028  _note_diff_command->remove(ev->note());
1029  }
1030 }
1031 
1032 void
1035  uint8_t val)
1036 {
1037  if (_note_diff_command) {
1038  _note_diff_command->change (ev->note(), property, val);
1039  }
1040 }
1041 
1042 void
1045  Evoral::Beats val)
1046 {
1047  if (_note_diff_command) {
1048  _note_diff_command->change (ev->note(), property, val);
1049  }
1050 }
1051 
1052 void
1053 MidiRegionView::apply_diff (bool as_subcommand)
1054 {
1055  bool add_or_remove;
1056  bool commit = false;
1057 
1058  if (!_note_diff_command) {
1059  return;
1060  }
1061 
1062  if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1063  // Mark all selected notes for selection when model reloads
1064  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1065  _marked_for_selection.insert((*i)->note());
1066  }
1067  }
1068 
1069  midi_view()->midi_track()->midi_playlist()->region_edited(
1071 
1072  if (as_subcommand) {
1074  } else {
1076  commit = true;
1077  }
1078 
1079  _note_diff_command = 0;
1080 
1081  if (add_or_remove) {
1082  _marked_for_selection.clear();
1083  }
1084 
1085  _marked_for_velocity.clear();
1086  if (commit) {
1088  }
1089 }
1090 
1091 void
1093 {
1094  delete _note_diff_command;
1095  _note_diff_command = 0;
1096  clear_selection();
1097 }
1098 
1099 NoteBase*
1101 {
1102  if (_optimization_iterator != _events.end()) {
1104  }
1105 
1106  if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1107  return *_optimization_iterator;
1108  }
1109 
1111  if ((*_optimization_iterator)->note() == note) {
1112  return *_optimization_iterator;
1113  }
1114  }
1115 
1116  return 0;
1117 }
1118 
1120 NoteBase*
1122 {
1123  Events::iterator it;
1124 
1125  for (it = _events.begin(); it != _events.end(); ++it) {
1126  if (*((*it)->note()) == note) {
1127  return *it;
1128  }
1129  }
1130 
1131  return 0;
1132 }
1133 
1134 void
1136 {
1137  MidiModel::Notes notes;
1138  _model->get_notes (notes, op, val, chan_mask);
1139 
1140  for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1141  NoteBase* cne = find_canvas_note (*n);
1142  if (cne) {
1143  e.push_back (cne);
1144  }
1145  }
1146 }
1147 
1148 void
1150 {
1151  if (_active_notes) {
1152  // Currently recording
1153  const framecnt_t zoom = trackview.editor().get_current_zoom();
1154  if (zoom != _last_display_zoom) {
1155  /* Update resolved canvas notes to reflect changes in zoom without
1156  touching model. Leave active notes (with length 0) alone since
1157  they are being extended. */
1158  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1159  if ((*i)->note()->length() > 0) {
1160  update_note(*i);
1161  }
1162  }
1163  _last_display_zoom = zoom;
1164  }
1165  return;
1166  }
1167 
1168  if (!_model) {
1169  return;
1170  }
1171 
1172  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1173  (*i)->invalidate ();
1174  }
1175 
1176  MidiModel::ReadLock lock(_model->read_lock());
1177 
1178  MidiModel::Notes& notes (_model->notes());
1179  _optimization_iterator = _events.begin();
1180 
1181  bool empty_when_starting = _events.empty();
1182 
1183  for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1184 
1185  boost::shared_ptr<NoteType> note (*n);
1186  NoteBase* cne;
1187  bool visible;
1188 
1189  if (note_in_region_range (note, visible)) {
1190 
1191  if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1192 
1193  cne->validate ();
1194  update_note (cne);
1195 
1196  if (visible) {
1197  cne->show ();
1198  } else {
1199  cne->hide ();
1200  }
1201 
1202  } else {
1203 
1204  cne = add_note (note, visible);
1205  }
1206 
1207  set<boost::shared_ptr<NoteType> >::iterator it;
1208  for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1209  if (*(*it) == *note) {
1210  add_to_selection (cne);
1211  }
1212  }
1213 
1214  } else {
1215 
1216  if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1217  cne->validate ();
1218  cne->hide ();
1219  }
1220  }
1221  }
1222 
1223  /* remove note items that are no longer valid */
1224 
1225  if (!empty_when_starting) {
1226  for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1227  if (!(*i)->valid ()) {
1228 
1229  for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1230  MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1231  if (gr) {
1232  gr->remove_note (*i);
1233  }
1234  }
1235 
1236  delete *i;
1237  i = _events.erase (i);
1238 
1239  } else {
1240  ++i;
1241  }
1242  }
1243  }
1244 
1245  _patch_changes.clear();
1246  _sys_exes.clear();
1247 
1248  display_sysexes();
1250 
1251  _marked_for_selection.clear ();
1252  _marked_for_velocity.clear ();
1253  _pending_note_selection.clear ();
1254 
1255  /* we may have caused _events to contain things out of order (e.g. if a note
1256  moved earlier or later). we don't generally need them in time order, but
1257  make a note that a sort is required for those cases that require it.
1258  */
1259 
1260  _sort_needed = true;
1261 }
1262 
1263 void
1265 {
1266  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1267  uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1268 
1269  for (uint8_t i = 0; i < 16; ++i) {
1270  display_patch_changes_on_channel (i, chn_mask & (1 << i));
1271  }
1272 }
1273 
1277 void
1278 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1279 {
1280  for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1281 
1282  if ((*i)->channel() != channel) {
1283  continue;
1284  }
1285 
1286  const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1287  add_canvas_patch_change (*i, patch_name, active_channel);
1288  }
1289 }
1290 
1291 void
1293 {
1294  bool have_periodic_system_messages = false;
1295  bool display_periodic_messages = true;
1296 
1297  if (!ARDOUR_UI::config()->get_never_display_periodic_midi()) {
1298 
1299  for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1302 
1303  if (mev) {
1304  if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1305  have_periodic_system_messages = true;
1306  break;
1307  }
1308  }
1309  }
1310 
1311  if (have_periodic_system_messages) {
1312  double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1313 
1314  /* get an approximate value for the number of samples per video frame */
1315 
1316  double video_frame = trackview.session()->frame_rate() * (1.0/30);
1317 
1318  /* if we are zoomed out beyond than the cutoff (i.e. more
1319  * frames per pixel than frames per 4 video frames), don't
1320  * show periodic sysex messages.
1321  */
1322 
1323  if (zoom > (video_frame*4)) {
1324  display_periodic_messages = false;
1325  }
1326  }
1327  } else {
1328  display_periodic_messages = false;
1329  }
1330 
1331  for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1332 
1335 
1336  Evoral::Beats time = (*i)->time();
1337 
1338  if (mev) {
1339  if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1340  if (!display_periodic_messages) {
1341  continue;
1342  }
1343  }
1344  }
1345 
1346  ostringstream str;
1347  str << hex;
1348  for (uint32_t b = 0; b < (*i)->size(); ++b) {
1349  str << int((*i)->buffer()[b]);
1350  if (b != (*i)->size() -1) {
1351  str << " ";
1352  }
1353  }
1354  string text = str.str();
1355 
1357 
1358  double height = midi_stream_view()->contents_height();
1359 
1360  // CAIROCANVAS: no longer passing *i (the sysex event) to the
1361  // SysEx canvas object!!!
1362 
1364  new SysEx (*this, _note_group, text, height, x, 1.0));
1365 
1366  // Show unless message is beyond the region bounds
1367  if (time - _region->start() >= _region->length() || time < _region->start()) {
1368  sysex->hide();
1369  } else {
1370  sysex->show();
1371  }
1372 
1373  _sys_exes.push_back(sysex);
1374  }
1375 }
1376 
1378 {
1379  in_destructor = true;
1380 
1382 
1384 
1385  delete _list_editor;
1386 
1387  RegionViewGoingAway (this); /* EMIT_SIGNAL */
1388 
1389  if (_active_notes) {
1390  end_write();
1391  }
1392 
1394 
1395  _selection.clear();
1396  clear_events (false);
1397 
1398  delete _note_group;
1399  delete _note_diff_command;
1400  delete _step_edit_cursor;
1401  delete _temporary_note_group;
1402 }
1403 
1404 void
1406 {
1407  RegionView::region_resized(what_changed);
1408 
1409  if (what_changed.contains (ARDOUR::Properties::position)) {
1411  set_duration(_region->length(), 0);
1412  if (_enable_display) {
1413  redisplay_model();
1414  }
1415  }
1416 
1417  if (what_changed.contains (ARDOUR::Properties::start) ||
1418  what_changed.contains (ARDOUR::Properties::position)) {
1420  }
1421 }
1422 
1423 void
1425 {
1427 
1428  if (_enable_display) {
1429  redisplay_model();
1430  }
1431 
1432  for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1433  if ((*x)->canvas_item()->width() >= _pixel_width) {
1434  (*x)->hide();
1435  } else {
1436  (*x)->show();
1437  }
1438  }
1439 
1442 }
1443 
1444 void
1446 {
1447  double old_height = _height;
1448  RegionView::set_height(height);
1449 
1450  apply_note_range (midi_stream_view()->lowest_note(),
1451  midi_stream_view()->highest_note(),
1452  height != old_height);
1453 
1454  if (name_text) {
1455  name_text->raise_to_top();
1456  }
1457 
1458  for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1459  (*x)->set_height (midi_stream_view()->contents_height());
1460  }
1461 
1462  if (_step_edit_cursor) {
1463  _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1464  }
1465 }
1466 
1467 
1471 void
1472 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1473 {
1474  if (!_enable_display) {
1475  return;
1476  }
1477 
1478  if (!force && _current_range_min == min && _current_range_max == max) {
1479  return;
1480  }
1481 
1482  _current_range_min = min;
1483  _current_range_max = max;
1484 
1485  for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1486  NoteBase* event = *i;
1487  boost::shared_ptr<NoteType> note (event->note());
1488 
1489  if (note->note() < _current_range_min ||
1490  note->note() > _current_range_max) {
1491  event->hide();
1492  } else {
1493  event->show();
1494  }
1495 
1496  if (Note* cnote = dynamic_cast<Note*>(event)) {
1497 
1498  const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1499  const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1500 
1501  if (y0 < 0 || y1 >= _height) {
1502  /* During DnD, the region uses the 'old/current'
1503  * midi_stream_view()'s range and its position/height calculation.
1504  *
1505  * Ideally DnD would decouple the midi_stream_view() for the
1506  * region(s) being dragged and set it to the target's range
1507  * (or in case of the drop-zone, FullRange).
1508  * but I don't see how this can be done without major rework.
1509  *
1510  * For now, just prevent visual bleeding of events in case
1511  * the target-track is smaller.
1512  */
1513  event->hide();
1514  continue;
1515  }
1516  cnote->set_y0 (y0);
1517  cnote->set_y1 (y1);
1518 
1519  } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1520  update_hit (chit);
1521  }
1522  }
1523 }
1524 
1525 GhostRegion*
1527 {
1528  double unit_position = _region->position () / samples_per_pixel;
1529  MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1530  MidiGhostRegion* ghost;
1531 
1532  if (mtv && mtv->midi_view()) {
1533  /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1534  to allow having midi notes on top of note lines and waveforms.
1535  */
1536  ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1537  } else {
1538  ghost = new MidiGhostRegion (tv, trackview, unit_position);
1539  }
1540 
1541  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1542  ghost->add_note(*i);
1543  }
1544 
1545  ghost->set_height ();
1547  ghosts.push_back (ghost);
1548 
1549  GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1550 
1551  return ghost;
1552 }
1553 
1554 
1557 void
1559 {
1560  if (_active_notes) {
1561  delete[] _active_notes;
1562  }
1563  _active_notes = new Note*[128];
1564  for (unsigned i = 0; i < 128; ++i) {
1565  _active_notes[i] = 0;
1566  }
1567 }
1568 
1569 
1572 void
1574 {
1575  delete[] _active_notes;
1576  _active_notes = 0;
1577  _marked_for_selection.clear();
1578  _marked_for_velocity.clear();
1579 }
1580 
1581 
1584 void
1586 {
1587  if (midi_view()->note_mode() != Sustained) {
1588  return;
1589  }
1590 
1591  if (_active_notes && _active_notes[note]) {
1592  /* Set note length so update_note() works. Note this is a local note
1593  for recording, not from a model, so we can safely mess with it. */
1594  _active_notes[note]->note()->set_length(
1595  end_time - _active_notes[note]->note()->time());
1596 
1597  /* End time is relative to the region being recorded. */
1598  const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1599 
1600  _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1601  _active_notes[note]->set_outline_all ();
1602  _active_notes[note] = 0;
1603  }
1604 }
1605 
1606 
1609 void
1611 {
1612  if (!_active_notes) {
1613  return;
1614  }
1615 
1616  for (unsigned i = 0; i < 128; ++i) {
1617  if (_active_notes[i]) {
1618  _active_notes[i]->set_x1(
1620  }
1621  }
1622 }
1623 
1624 void
1626 {
1627  if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1628  return;
1629  }
1630 
1631  RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1632 
1633  if (!route_ui || !route_ui->midi_track()) {
1634  return;
1635  }
1636 
1637  NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1638  np->add (note);
1639  np->play ();
1640 
1641  /* NotePlayer deletes itself */
1642 }
1643 
1644 void
1646 {
1647  const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1648  start_playing_midi_chord(notes);
1649 }
1650 
1651 void
1653 {
1654  if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1655  return;
1656  }
1657 
1658  RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1659 
1660  if (!route_ui || !route_ui->midi_track()) {
1661  return;
1662  }
1663 
1664  NotePlayer* player = new NotePlayer (route_ui->midi_track());
1665 
1666  for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1667  player->add (*n);
1668  }
1669 
1670  player->play ();
1671 }
1672 
1673 
1674 bool
1676 {
1677  /* This is imprecise due to all the conversion conversion involved, so only
1678  hide notes if they seem to start more than one tick before the start. */
1679  const framecnt_t tick_frames = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate());
1680  const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1681  const bool outside = ((note_start_frames <= -tick_frames) ||
1682  (note_start_frames >= _region->length()));
1683 
1684  visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1685  (note->note() <= midi_stream_view()->highest_note());
1686 
1687  return !outside;
1688 }
1689 
1690 void
1691 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1692 {
1693  Note* sus = NULL;
1694  Hit* hit = NULL;
1695  if ((sus = dynamic_cast<Note*>(note))) {
1696  update_sustained(sus, update_ghost_regions);
1697  } else if ((hit = dynamic_cast<Hit*>(note))) {
1698  update_hit(hit, update_ghost_regions);
1699  }
1700 }
1701 
1706 void
1707 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1708 {
1709  boost::shared_ptr<NoteType> note = ev->note();
1710  const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1711  const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1712 
1713  ev->set_x0 (x);
1714  ev->set_y0 (y0);
1715 
1716  /* trim note display to not overlap the end of its region */
1717 
1718  if (note->length() > 0) {
1719  const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1720  ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
1721  } else {
1723  }
1724 
1725  ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1726 
1727  if (!note->length()) {
1728  if (_active_notes && note->note() < 128) {
1729  Note* const old_rect = _active_notes[note->note()];
1730  if (old_rect) {
1731  /* There is an active note on this key, so we have a stuck
1732  note. Finish the old rectangle here. */
1733  old_rect->set_x1 (x);
1734  old_rect->set_outline_all ();
1735  }
1736  _active_notes[note->note()] = ev;
1737  }
1738  /* outline all but right edge */
1739  ev->set_outline_what (ArdourCanvas::Rectangle::What (
1740  ArdourCanvas::Rectangle::TOP|
1741  ArdourCanvas::Rectangle::LEFT|
1742  ArdourCanvas::Rectangle::BOTTOM));
1743  } else {
1744  /* outline all edges */
1745  ev->set_outline_all ();
1746  }
1747 
1748  // Update color in case velocity has changed
1749  ev->set_fill_color(ev->base_color());
1750  ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1751 
1752  if (update_ghost_regions) {
1753  for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1754  MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1755  if (gr) {
1756  gr->update_note (ev);
1757  }
1758  }
1759  }
1760 }
1761 
1762 void
1763 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1764 {
1765  boost::shared_ptr<NoteType> note = ev->note();
1766 
1767  const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1768  const double x = trackview.editor().sample_to_pixel(note_start_frames);
1769  const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1770  const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1771 
1772  // see DnD note in MidiRegionView::apply_note_range() above
1773  if (y <= 0 || y >= _height) {
1774  ev->hide();
1775  } else {
1776  ev->show();
1777  }
1778 
1779  ev->set_position (ArdourCanvas::Duple (x, y));
1780  ev->set_height (diamond_size);
1781 
1782  // Update color in case velocity has changed
1783  ev->set_fill_color(ev->base_color());
1784  ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1785 
1786  if (update_ghost_regions) {
1787  for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1788  MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1789  if (gr) {
1790  gr->update_note (ev);
1791  }
1792  }
1793  }
1794 }
1795 
1802 NoteBase*
1804 {
1805  NoteBase* event = 0;
1806 
1807  if (midi_view()->note_mode() == Sustained) {
1808 
1809  Note* ev_rect = new Note (*this, _note_group, note);
1810 
1811  update_sustained (ev_rect);
1812 
1813  event = ev_rect;
1814 
1815  } else if (midi_view()->note_mode() == Percussive) {
1816 
1817  const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1818 
1819  Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1820 
1821  update_hit (ev_diamond);
1822 
1823  event = ev_diamond;
1824 
1825  } else {
1826  event = 0;
1827  }
1828 
1829  if (event) {
1830  MidiGhostRegion* gr;
1831 
1832  for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1833  if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1834  gr->add_note(event);
1835  }
1836  }
1837 
1838  if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1839  note_selected(event, true);
1840  }
1841 
1842  if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1843  event->show_velocity();
1844  }
1845 
1846  event->on_channel_selection_change (get_selected_channels());
1847  _events.push_back(event);
1848 
1849  if (visible) {
1850  event->show();
1851  } else {
1852  event->hide ();
1853  }
1854  }
1855 
1856  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1857  MidiStreamView* const view = mtv->midi_view();
1858 
1859  view->update_note_range (note->note());
1860  return event;
1861 }
1862 
1863 void
1864 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1865  Evoral::Beats pos, Evoral::Beats len)
1866 {
1867  boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1868 
1869  /* potentially extend region to hold new note */
1870 
1871  framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1872  framepos_t region_end = _region->last_frame();
1873 
1874  if (end_frame > region_end) {
1875  _region->set_length (end_frame - _region->position());
1876  }
1877 
1878  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1879  MidiStreamView* const view = mtv->midi_view();
1880 
1881  view->update_note_range(new_note->note());
1882 
1883  _marked_for_selection.clear ();
1884 
1885  start_note_diff_command (_("step add"));
1886 
1887  clear_selection ();
1888  note_diff_add_note (new_note, true, false);
1889 
1890  apply_diff();
1891 
1892  // last_step_edit_note = new_note;
1893 }
1894 
1895 void
1897 {
1898  change_note_lengths (false, false, beats, false, true);
1899 }
1900 
1906 void
1907 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1908 {
1909  framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1910  const double x = trackview.editor().sample_to_pixel (region_frames);
1911 
1912  double const height = midi_stream_view()->contents_height();
1913 
1914  // CAIROCANVAS: active_channel info removed from PatcChange constructor
1915  // so we need to do something more sophisticated to keep its color
1916  // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1917  // up to date.
1918 
1920  new PatchChange(*this, group,
1921  displaytext,
1922  height,
1923  x, 1.0,
1924  instrument_info(),
1925  patch));
1926 
1927  if (patch_change->item().width() < _pixel_width) {
1928  // Show unless patch change is beyond the region bounds
1929  if (region_frames < 0 || region_frames >= _region->length()) {
1930  patch_change->hide();
1931  } else {
1932  patch_change->show();
1933  }
1934  } else {
1935  patch_change->hide ();
1936  }
1937 
1938  _patch_changes.push_back (patch_change);
1939 }
1940 
1941 MIDI::Name::PatchPrimaryKey
1942 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1943 {
1944  return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1945 }
1946 
1948 static bool
1950 {
1951  return pc->time() <= time && pc->channel() == channel;
1952 }
1953 
1954 void
1955 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1956 {
1957  // The earliest event not before time
1958  MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1959 
1960  // Go backwards until we find the latest PC for this channel, or the start
1961  while (i != _model->patch_changes().begin() &&
1962  (i == _model->patch_changes().end() ||
1963  !patch_applies(*i, time, channel))) {
1964  --i;
1965  }
1966 
1967  if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1968  key.set_bank((*i)->bank());
1969  key.set_program((*i)->program ());
1970  } else {
1971  key.set_bank(0);
1972  key.set_program(0);
1973  }
1974 }
1975 
1976 void
1977 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1978 {
1979  string name = _("alter patch change");
1982 
1983  if (pc.patch()->program() != new_patch.program()) {
1984  c->change_program (pc.patch (), new_patch.program());
1985  }
1986 
1987  int const new_bank = new_patch.bank();
1988  if (pc.patch()->bank() != new_bank) {
1989  c->change_bank (pc.patch (), new_bank);
1990  }
1991 
1994 
1995  _patch_changes.clear ();
1997 }
1998 
1999 void
2000 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2001 {
2002  string name = _("alter patch change");
2005 
2006  if (old_change->time() != new_change.time()) {
2007  c->change_time (old_change, new_change.time());
2008  }
2009 
2010  if (old_change->channel() != new_change.channel()) {
2011  c->change_channel (old_change, new_change.channel());
2012  }
2013 
2014  if (old_change->program() != new_change.program()) {
2015  c->change_program (old_change, new_change.program());
2016  }
2017 
2018  if (old_change->bank() != new_change.bank()) {
2019  c->change_bank (old_change, new_change.bank());
2020  }
2021 
2024 
2025  _patch_changes.clear ();
2027 }
2028 
2034 void
2036 {
2037  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2038  string name = _("add patch change");
2039 
2042  c->add (MidiModel::PatchChangePtr (
2045  mtv->get_channel_for_add(), patch.program(), patch.bank()
2046  )
2047  )
2048  );
2049 
2052 
2053  _patch_changes.clear ();
2055 }
2056 
2057 void
2059 {
2060  trackview.editor().begin_reversible_command (_("move patch change"));
2062  c->change_time (pc.patch (), t);
2065 
2066  _patch_changes.clear ();
2068 }
2069 
2070 void
2072 {
2073  trackview.editor().begin_reversible_command (_("delete patch change"));
2075  c->remove (pc->patch ());
2078 
2079  _patch_changes.clear ();
2081 }
2082 
2083 void
2084 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2085 {
2086  MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2087  if (bank) {
2088  key.set_bank(key.bank() + delta);
2089  } else {
2090  key.set_program(key.program() + delta);
2091  }
2092  change_patch_change(patch, key);
2093 }
2094 
2095 void
2097 {
2098  if (_selection.empty()) {
2099  return;
2100  }
2101 
2102  _selection.erase (cne);
2103 }
2104 
2105 void
2107 {
2108  if (_selection.empty()) {
2109  return;
2110  }
2111 
2112  start_note_diff_command (_("delete selection"));
2113 
2114  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2115  if ((*i)->selected()) {
2116  _note_diff_command->remove((*i)->note());
2117  }
2118  }
2119 
2120  _selection.clear();
2121 
2122  apply_diff ();
2123 }
2124 
2125 void
2127 {
2128  start_note_diff_command (_("delete note"));
2130  apply_diff ();
2131 
2133 }
2134 
2135 void
2137 {
2138  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2139  if ((*i) != ev) {
2140  Selection::iterator tmp = i;
2141  ++tmp;
2142 
2143  (*i)->set_selected (false);
2144  (*i)->hide_velocity ();
2145  _selection.erase (i);
2146 
2147  i = tmp;
2148  } else {
2149  ++i;
2150  }
2151  }
2152 
2153  if (!ev && _entered) {
2154  // Clearing selection entirely, ungrab keyboard
2155  Keyboard::magic_widget_drop_focus();
2156  _grabbed_keyboard = false;
2157  }
2158 
2159  /* this does not change the status of this regionview w.r.t the editor
2160  selection.
2161  */
2162 
2163  if (signal) {
2164  SelectionCleared (this); /* EMIT SIGNAL */
2165  }
2166 }
2167 
2168 void
2170 {
2171  const bool selection_was_empty = _selection.empty();
2172 
2174 
2175  /* don't bother with checking to see if we should remove this
2176  regionview from the editor selection, since we're about to add
2177  another note, and thus put/keep this regionview in the editor
2178  selection anyway.
2179  */
2180 
2181  if (!ev->selected()) {
2182  add_to_selection (ev);
2183  if (selection_was_empty && _entered) {
2184  // Grab keyboard for moving notes with arrow keys
2185  Keyboard::magic_widget_grab_focus();
2186  _grabbed_keyboard = true;
2187  }
2188  }
2189 }
2190 
2191 void
2193 {
2194  clear_selection ();
2195 
2196  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2197  add_to_selection (*i);
2198  }
2199 }
2200 
2201 void
2203 {
2204  clear_selection ();
2205 
2206  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2207  framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2208  if (t >= start && t <= end) {
2209  add_to_selection (*i);
2210  }
2211  }
2212 }
2213 
2214 void
2216 {
2217  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2218  if ((*i)->selected()) {
2220  } else {
2221  add_to_selection (*i);
2222  }
2223  }
2224 }
2225 
2229 void
2231 {
2232  NoteBase* cne;
2233  list<boost::shared_ptr<NoteType> >::iterator n;
2234 
2235  for (n = notes.begin(); n != notes.end(); ++n) {
2236  if ((cne = find_canvas_note(*(*n))) != 0) {
2237  add_to_selection (cne);
2238  } else {
2239  _pending_note_selection.insert(*n);
2240  }
2241  }
2242 }
2243 
2244 void
2245 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2246 {
2247  bool have_selection = !_selection.empty();
2248  uint8_t low_note = 127;
2249  uint8_t high_note = 0;
2250  MidiModel::Notes& notes (_model->notes());
2251  _optimization_iterator = _events.begin();
2252 
2253  if (extend && !have_selection) {
2254  extend = false;
2255  }
2256 
2257  /* scan existing selection to get note range */
2258 
2259  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2260  if ((*i)->note()->note() < low_note) {
2261  low_note = (*i)->note()->note();
2262  }
2263  if ((*i)->note()->note() > high_note) {
2264  high_note = (*i)->note()->note();
2265  }
2266  }
2267 
2268  if (!add) {
2269  clear_selection ();
2270 
2271  if (!extend && (low_note == high_note) && (high_note == notenum)) {
2272  /* only note previously selected is the one we are
2273  * reselecting. treat this as cancelling the selection.
2274  */
2275  return;
2276  }
2277  }
2278 
2279  if (extend) {
2280  low_note = min (low_note, notenum);
2281  high_note = max (high_note, notenum);
2282  }
2283 
2284  _no_sound_notes = true;
2285 
2286  for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2287 
2288  boost::shared_ptr<NoteType> note (*n);
2289  NoteBase* cne;
2290  bool select = false;
2291 
2292  if (((1 << note->channel()) & channel_mask) != 0) {
2293  if (extend) {
2294  if ((note->note() >= low_note && note->note() <= high_note)) {
2295  select = true;
2296  }
2297  } else if (note->note() == notenum) {
2298  select = true;
2299  }
2300  }
2301 
2302  if (select) {
2303  if ((cne = find_canvas_note (note)) != 0) {
2304  // extend is false because we've taken care of it,
2305  // since it extends by time range, not pitch.
2306  note_selected (cne, add, false);
2307  }
2308  }
2309 
2310  add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2311 
2312  }
2313 
2314  _no_sound_notes = false;
2315 }
2316 
2317 void
2318 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2319 {
2320  MidiModel::Notes& notes (_model->notes());
2321  _optimization_iterator = _events.begin();
2322 
2323  for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2324 
2325  boost::shared_ptr<NoteType> note (*n);
2326  NoteBase* cne;
2327 
2328  if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2329  if ((cne = find_canvas_note (note)) != 0) {
2330  if (cne->selected()) {
2331  note_deselected (cne);
2332  } else {
2333  note_selected (cne, true, false);
2334  }
2335  }
2336  }
2337  }
2338 }
2339 
2340 void
2341 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2342 {
2343  if (!add) {
2345  if (!_selection.empty()) {
2347  editor.get_selection().add (this);
2348  }
2349  }
2350 
2351  if (!extend) {
2352 
2353  if (!ev->selected()) {
2354  add_to_selection (ev);
2355  }
2356 
2357  } else {
2358  /* find end of latest note selected, select all between that and the start of "ev" */
2359 
2360  Evoral::Beats earliest = Evoral::MaxBeats;
2361  Evoral::Beats latest = Evoral::Beats();
2362 
2363  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2364  if ((*i)->note()->end_time() > latest) {
2365  latest = (*i)->note()->end_time();
2366  }
2367  if ((*i)->note()->time() < earliest) {
2368  earliest = (*i)->note()->time();
2369  }
2370  }
2371 
2372  if (ev->note()->end_time() > latest) {
2373  latest = ev->note()->end_time();
2374  }
2375 
2376  if (ev->note()->time() < earliest) {
2377  earliest = ev->note()->time();
2378  }
2379 
2380  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2381 
2382  /* find notes entirely within OR spanning the earliest..latest range */
2383 
2384  if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2385  ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2386  add_to_selection (*i);
2387  }
2388 
2389  }
2390  }
2391 }
2392 
2393 void
2395 {
2396  remove_from_selection (ev);
2397 }
2398 
2399 void
2400 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2401 {
2403 
2404  // Convert to local coordinates
2405  const framepos_t p = _region->position();
2406  const double y = midi_view()->y_position();
2407  const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2408  const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2409  const double y0 = max(0.0, gy0 - y);
2410  const double y1 = max(0.0, gy1 - y);
2411 
2412  // TODO: Make this faster by storing the last updated selection rect, and only
2413  // adjusting things that are in the area that appears/disappeared.
2414  // We probably need a tree to be able to find events in O(log(n)) time.
2415 
2416  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2417  if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2418  // Rectangles intersect
2419  if (!(*i)->selected()) {
2420  add_to_selection (*i);
2421  }
2422  } else if ((*i)->selected() && !extend) {
2423  // Rectangles do not intersect
2424  remove_from_selection (*i);
2425  }
2426  }
2427 
2428  typedef RouteTimeAxisView::AutomationTracks ATracks;
2429  typedef std::list<Selectable*> Selectables;
2430 
2431  /* Add control points to selection. */
2432  const ATracks& atracks = midi_view()->automation_tracks();
2433  Selectables selectables;
2434  editor.get_selection().clear_points();
2435  for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2436  a->second->get_selectables(start, end, gy0, gy1, selectables);
2437  for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2438  ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2439  if (cp) {
2440  editor.get_selection().add(cp);
2441  }
2442  }
2443  a->second->set_selected_points(editor.get_selection().points);
2444  }
2445 }
2446 
2447 void
2448 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2449 {
2450  if (y1 > y2) {
2451  swap (y1, y2);
2452  }
2453 
2454  // TODO: Make this faster by storing the last updated selection rect, and only
2455  // adjusting things that are in the area that appears/disappeared.
2456  // We probably need a tree to be able to find events in O(log(n)) time.
2457 
2458  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2459  if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2460  // within y- (note-) range
2461  if (!(*i)->selected()) {
2462  add_to_selection (*i);
2463  }
2464  } else if ((*i)->selected() && !extend) {
2465  remove_from_selection (*i);
2466  }
2467  }
2468 }
2469 
2470 void
2472 {
2473  Selection::iterator i = _selection.find (ev);
2474 
2475  if (i != _selection.end()) {
2476  _selection.erase (i);
2477  if (_selection.empty() && _grabbed_keyboard) {
2478  // Ungrab keyboard
2479  Keyboard::magic_widget_drop_focus();
2480  _grabbed_keyboard = false;
2481  }
2482  }
2483 
2484  ev->set_selected (false);
2485  ev->hide_velocity ();
2486 
2487  if (_selection.empty()) {
2489  editor.get_selection().remove (this);
2490  }
2491 }
2492 
2493 void
2495 {
2496  const bool selection_was_empty = _selection.empty();
2497 
2498  if (_selection.insert (ev).second) {
2499  ev->set_selected (true);
2500  start_playing_midi_note ((ev)->note());
2501  if (selection_was_empty && _entered) {
2502  // Grab keyboard for moving notes with arrow keys
2503  Keyboard::magic_widget_grab_focus();
2504  _grabbed_keyboard = true;
2505  }
2506  }
2507 
2508  if (selection_was_empty) {
2510  editor.get_selection().add (this);
2511  }
2512 }
2513 
2514 void
2515 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2516 {
2517  typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2518  PossibleChord to_play;
2519  Evoral::Beats earliest = Evoral::MaxBeats;
2520 
2521  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2522  if ((*i)->note()->time() < earliest) {
2523  earliest = (*i)->note()->time();
2524  }
2525  }
2526 
2527  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2528  if ((*i)->note()->time() == earliest) {
2529  to_play.push_back ((*i)->note());
2530  }
2531  (*i)->move_event(dx, dy);
2532  }
2533 
2534  if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) {
2535 
2536  if (to_play.size() > 1) {
2537 
2538  PossibleChord shifted;
2539 
2540  for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2541  boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2542  moved_note->set_note (moved_note->note() + cumulative_dy);
2543  shifted.push_back (moved_note);
2544  }
2545 
2546  start_playing_midi_chord (shifted);
2547 
2548  } else if (!to_play.empty()) {
2549 
2550  boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2551  moved_note->set_note (moved_note->note() + cumulative_dy);
2552  start_playing_midi_note (moved_note);
2553  }
2554  }
2555 }
2556 
2557 void
2559 {
2560  uint8_t lowest_note_in_selection = 127;
2561  uint8_t highest_note_in_selection = 0;
2562  uint8_t highest_note_difference = 0;
2563 
2564  // find highest and lowest notes first
2565 
2566  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2567  uint8_t pitch = (*i)->note()->note();
2568  lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2569  highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2570  }
2571 
2572  /*
2573  cerr << "dnote: " << (int) dnote << endl;
2574  cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2575  << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2576  cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2577  << int(highest_note_in_selection) << endl;
2578  cerr << "selection size: " << _selection.size() << endl;
2579  cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2580  */
2581 
2582  // Make sure the note pitch does not exceed the MIDI standard range
2583  if (highest_note_in_selection + dnote > 127) {
2584  highest_note_difference = highest_note_in_selection - 127;
2585  }
2586 
2587  start_note_diff_command (_("move notes"));
2588 
2589  for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2590 
2591  framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2592  Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
2593 
2594  if (new_time < 0) {
2595  continue;
2596  }
2597 
2598  note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2599 
2600  uint8_t original_pitch = (*i)->note()->note();
2601  uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2602 
2603  // keep notes in standard midi range
2604  clamp_to_0_127(new_pitch);
2605 
2606  lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2607  highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2608 
2609  note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2610  }
2611 
2612  apply_diff();
2613 
2614  // care about notes being moved beyond the upper/lower bounds on the canvas
2615  if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2616  highest_note_in_selection > midi_stream_view()->highest_note()) {
2618  }
2619 }
2620 
2624 framepos_t
2626 {
2628  return snap_frame_to_frame (editor.pixel_to_sample (x));
2629 }
2630 
2634 double
2636 {
2637  return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x));
2638 }
2639 
2640 double
2642 {
2643  framepos_t region_frame = get_position();
2644  return trackview.editor().sample_to_pixel(region_frame);
2645 }
2646 
2647 double
2649 {
2651  return trackview.editor().sample_to_pixel(frame);
2652 }
2653 
2654 framepos_t
2656 {
2657  /* the time converter will return the frame corresponding to `beats'
2658  relative to the start of the source. The start of the source
2659  is an implied position given by region->position - region->start
2660  */
2661  const framepos_t source_start = _region->position() - _region->start();
2662  return source_start + _source_relative_time_converter.to (beats);
2663 }
2664 
2667 {
2668  /* the `frames' argument needs to be converted into a frame count
2669  relative to the start of the source before being passed in to the
2670  converter.
2671  */
2672  const framepos_t source_start = _region->position() - _region->start();
2673  return _source_relative_time_converter.from (frames - source_start);
2674 }
2675 
2676 framepos_t
2678 {
2679  return _region_relative_time_converter.to(beats);
2680 }
2681 
2684 {
2685  return _region_relative_time_converter.from(frames);
2686 }
2687 
2688 void
2690 {
2691  _resize_data.clear();
2692 
2693  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2694  Note *note = dynamic_cast<Note*> (*i);
2695 
2696  // only insert CanvasNotes into the map
2697  if (note) {
2698  NoteResizeData *resize_data = new NoteResizeData();
2699  resize_data->note = note;
2700 
2701  // create a new SimpleRect from the note which will be the resize preview
2702  ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2703  ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2704 
2705  // calculate the colors: get the color settings
2706  uint32_t fill_color = UINT_RGBA_CHANGE_A(
2707  ARDOUR_UI::config()->color ("midi note selected"),
2708  128);
2709 
2710  // make the resize preview notes more transparent and bright
2711  fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2712 
2713  // calculate color based on note velocity
2714  resize_rect->set_fill_color (UINT_INTERPOLATE(
2715  NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2716  fill_color,
2717  0.85));
2718 
2719  resize_rect->set_outline_color (NoteBase::calculate_outline (
2720  ARDOUR_UI::config()->color ("midi note selected")));
2721 
2722  resize_data->resize_rect = resize_rect;
2723  _resize_data.push_back(resize_data);
2724  }
2725  }
2726 }
2727 
2737 void
2738 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2739 {
2740  bool cursor_set = false;
2741 
2742  for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2743  ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2744  Note* canvas_note = (*i)->note;
2745  double current_x;
2746 
2747  if (at_front) {
2748  if (relative) {
2749  current_x = canvas_note->x0() + delta_x;
2750  } else {
2751  current_x = primary->x0() + delta_x;
2752  }
2753  } else {
2754  if (relative) {
2755  current_x = canvas_note->x1() + delta_x;
2756  } else {
2757  current_x = primary->x1() + delta_x;
2758  }
2759  }
2760 
2761  if (current_x < 0) {
2762  // This works even with snapping because RegionView::snap_frame_to_frame()
2763  // snaps forward if the snapped sample is before the beginning of the region
2764  current_x = 0;
2765  }
2766  if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2767  current_x = trackview.editor().sample_to_pixel(_region->length());
2768  }
2769 
2770  if (at_front) {
2771  resize_rect->set_x0 (snap_to_pixel(current_x));
2772  resize_rect->set_x1 (canvas_note->x1());
2773  } else {
2774  resize_rect->set_x1 (snap_to_pixel(current_x));
2775  resize_rect->set_x0 (canvas_note->x0());
2776  }
2777 
2778  if (!cursor_set) {
2779  const double snapped_x = snap_pixel_to_sample (current_x);
2780  Evoral::Beats beats = region_frames_to_region_beats (snapped_x);
2781  Evoral::Beats len = Evoral::Beats();
2782 
2783  if (at_front) {
2784  if (beats < canvas_note->note()->end_time()) {
2785  len = canvas_note->note()->time() - beats;
2786  len += canvas_note->note()->length();
2787  }
2788  } else {
2789  if (beats >= canvas_note->note()->time()) {
2790  len = beats - canvas_note->note()->time();
2791  }
2792  }
2793 
2794  len = std::max(Evoral::Beats(1 / 512.0), len);
2795 
2796  char buf[16];
2797  snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2798  show_verbose_cursor (buf, 0, 0);
2799 
2800  cursor_set = true;
2801  }
2802 
2803  }
2804 }
2805 
2806 
2810 void
2811 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2812 {
2813  _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2814 
2815  for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2816  Note* canvas_note = (*i)->note;
2817  ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2818 
2819  /* Get the new x position for this resize, which is in pixels relative
2820  * to the region position.
2821  */
2822 
2823  double current_x;
2824 
2825  if (at_front) {
2826  if (relative) {
2827  current_x = canvas_note->x0() + delta_x;
2828  } else {
2829  current_x = primary->x0() + delta_x;
2830  }
2831  } else {
2832  if (relative) {
2833  current_x = canvas_note->x1() + delta_x;
2834  } else {
2835  current_x = primary->x1() + delta_x;
2836  }
2837  }
2838 
2839  if (current_x < 0) {
2840  current_x = 0;
2841  }
2842  if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2843  current_x = trackview.editor().sample_to_pixel(_region->length());
2844  }
2845 
2846  /* Convert that to a frame within the source */
2847  current_x = snap_pixel_to_sample (current_x) + _region->start ();
2848 
2849  /* and then to beats */
2850  const Evoral::Beats x_beats = region_frames_to_region_beats (current_x);
2851 
2852  if (at_front && x_beats < canvas_note->note()->end_time()) {
2853  note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats);
2854 
2855  Evoral::Beats len = canvas_note->note()->time() - x_beats;
2856  len += canvas_note->note()->length();
2857 
2858  if (!!len) {
2859  note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2860  }
2861  }
2862 
2863  if (!at_front) {
2864  const Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2865  x_beats - canvas_note->note()->time());
2866  note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2867  }
2868 
2869  delete resize_rect;
2870  delete (*i);
2871  }
2872 
2873  _resize_data.clear();
2874  apply_diff(true);
2875 }
2876 
2877 void
2879 {
2880  for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2881  delete (*i)->resize_rect;
2882  delete *i;
2883  }
2884 
2885  _resize_data.clear ();
2886 }
2887 
2888 void
2890 {
2891  uint8_t new_velocity;
2892 
2893  if (relative) {
2894  new_velocity = event->note()->velocity() + velocity;
2895  clamp_to_0_127(new_velocity);
2896  } else {
2897  new_velocity = velocity;
2898  }
2899 
2900  event->set_selected (event->selected()); // change color
2901 
2902  note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2903 }
2904 
2905 void
2907 {
2908  uint8_t new_note;
2909 
2910  if (relative) {
2911  new_note = event->note()->note() + note;
2912  } else {
2913  new_note = note;
2914  }
2915 
2916  clamp_to_0_127 (new_note);
2917  note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2918 }
2919 
2920 void
2922 {
2923  bool change_start = false;
2924  bool change_length = false;
2925  Evoral::Beats new_start;
2926  Evoral::Beats new_length;
2927 
2928  /* NOTE: the semantics of the two delta arguments are slightly subtle:
2929 
2930  front_delta: if positive - move the start of the note later in time (shortening it)
2931  if negative - move the start of the note earlier in time (lengthening it)
2932 
2933  end_delta: if positive - move the end of the note later in time (lengthening it)
2934  if negative - move the end of the note earlier in time (shortening it)
2935  */
2936 
2937  if (!!front_delta) {
2938  if (front_delta < 0) {
2939 
2940  if (event->note()->time() < -front_delta) {
2941  new_start = Evoral::Beats();
2942  } else {
2943  new_start = event->note()->time() + front_delta; // moves earlier
2944  }
2945 
2946  /* start moved toward zero, so move the end point out to where it used to be.
2947  Note that front_delta is negative, so this increases the length.
2948  */
2949 
2950  new_length = event->note()->length() - front_delta;
2951  change_start = true;
2952  change_length = true;
2953 
2954  } else {
2955 
2956  Evoral::Beats new_pos = event->note()->time() + front_delta;
2957 
2958  if (new_pos < event->note()->end_time()) {
2959  new_start = event->note()->time() + front_delta;
2960  /* start moved toward the end, so move the end point back to where it used to be */
2961  new_length = event->note()->length() - front_delta;
2962  change_start = true;
2963  change_length = true;
2964  }
2965  }
2966 
2967  }
2968 
2969  if (!!end_delta) {
2970  bool can_change = true;
2971  if (end_delta < 0) {
2972  if (event->note()->length() < -end_delta) {
2973  can_change = false;
2974  }
2975  }
2976 
2977  if (can_change) {
2978  new_length = event->note()->length() + end_delta;
2979  change_length = true;
2980  }
2981  }
2982 
2983  if (change_start) {
2984  note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2985  }
2986 
2987  if (change_length) {
2988  note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2989  }
2990 }
2991 
2992 void
2994 {
2995  uint8_t new_channel;
2996 
2997  if (relative) {
2998  if (chn < 0.0) {
2999  if (event->note()->channel() < -chn) {
3000  new_channel = 0;
3001  } else {
3002  new_channel = event->note()->channel() + chn;
3003  }
3004  } else {
3005  new_channel = event->note()->channel() + chn;
3006  }
3007  } else {
3008  new_channel = (uint8_t) chn;
3009  }
3010 
3011  note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3012 }
3013 
3014 void
3016 {
3017  Evoral::Beats new_time;
3018 
3019  if (relative) {
3020  if (delta < 0.0) {
3021  if (event->note()->time() < -delta) {
3022  new_time = Evoral::Beats();
3023  } else {
3024  new_time = event->note()->time() + delta;
3025  }
3026  } else {
3027  new_time = event->note()->time() + delta;
3028  }
3029  } else {
3030  new_time = delta;
3031  }
3032 
3033  note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3034 }
3035 
3036 void
3038 {
3039  note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3040 }
3041 
3042 void
3043 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3044 {
3045  int8_t delta;
3046  int8_t value = 0;
3047 
3048  if (_selection.empty()) {
3049  return;
3050  }
3051 
3052  if (fine) {
3053  delta = 1;
3054  } else {
3055  delta = 10;
3056  }
3057 
3058  if (!up) {
3059  delta = -delta;
3060  }
3061 
3062  if (!allow_smush) {
3063  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3064  if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3065  goto cursor_label;
3066  }
3067  }
3068  }
3069 
3070  start_note_diff_command (_("change velocities"));
3071 
3072  for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3073  Selection::iterator next = i;
3074  ++next;
3075 
3076  if (all_together) {
3077  if (i == _selection.begin()) {
3078  change_note_velocity (*i, delta, true);
3079  value = (*i)->note()->velocity() + delta;
3080  } else {
3081  change_note_velocity (*i, value, false);
3082  }
3083 
3084  } else {
3085  change_note_velocity (*i, delta, true);
3086  }
3087 
3088  i = next;
3089  }
3090 
3091  apply_diff();
3092 
3093  cursor_label:
3094  if (!_selection.empty()) {
3095  char buf[24];
3096  snprintf (buf, sizeof (buf), "Vel %d",
3097  (int) (*_selection.begin())->note()->velocity());
3098  show_verbose_cursor (buf, 10, 10);
3099  }
3100 }
3101 
3102 
3103 void
3104 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3105 {
3106  if (_selection.empty()) {
3107  return;
3108  }
3109 
3110  int8_t delta;
3111 
3112  if (fine) {
3113  delta = 1;
3114  } else {
3115  delta = 12;
3116  }
3117 
3118  if (!up) {
3119  delta = -delta;
3120  }
3121 
3122  if (!allow_smush) {
3123  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3124  if (!up) {
3125  if ((int8_t) (*i)->note()->note() + delta <= 0) {
3126  return;
3127  }
3128  } else {
3129  if ((int8_t) (*i)->note()->note() + delta > 127) {
3130  return;
3131  }
3132  }
3133  }
3134  }
3135 
3136  start_note_diff_command (_("transpose"));
3137 
3138  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3139  Selection::iterator next = i;
3140  ++next;
3141  change_note_note (*i, delta, true);
3142  i = next;
3143  }
3144 
3145  apply_diff ();
3146 }
3147 
3148 void
3149 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3150 {
3151  if (!delta) {
3152  if (fine) {
3153  delta = Evoral::Beats(1.0/128.0);
3154  } else {
3155  /* grab the current grid distance */
3156  delta = get_grid_beats(_region->position());
3157  }
3158  }
3159 
3160  if (shorter) {
3161  delta = -delta;
3162  }
3163 
3164  start_note_diff_command (_("change note lengths"));
3165 
3166  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3167  Selection::iterator next = i;
3168  ++next;
3169 
3170  /* note the negation of the delta for start */
3171 
3172  trim_note (*i,
3173  (start ? -delta : Evoral::Beats()),
3174  (end ? delta : Evoral::Beats()));
3175  i = next;
3176  }
3177 
3178  apply_diff ();
3179 
3180 }
3181 
3182 void
3183 MidiRegionView::nudge_notes (bool forward, bool fine)
3184 {
3185  if (_selection.empty()) {
3186  return;
3187  }
3188 
3189  /* pick a note as the point along the timeline to get the nudge distance.
3190  its not necessarily the earliest note, so we may want to pull the notes out
3191  into a vector and sort before using the first one.
3192  */
3193 
3194  const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3195  Evoral::Beats delta;
3196 
3197  if (!fine) {
3198 
3199  /* non-fine, move by 1 bar regardless of snap */
3201 
3202  } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3203 
3204  /* grid is off - use nudge distance */
3205 
3206  framepos_t unused;
3207  const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3208  delta = region_frames_to_region_beats (fabs ((double)distance));
3209 
3210  } else {
3211 
3212  /* use grid */
3213 
3214  framepos_t next_pos = ref_point;
3215 
3216  if (forward) {
3217  if (max_framepos - 1 < next_pos) {
3218  next_pos += 1;
3219  }
3220  } else {
3221  if (next_pos == 0) {
3222  return;
3223  }
3224  next_pos -= 1;
3225  }
3226 
3227  trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3228  const framecnt_t distance = ref_point - next_pos;
3229  delta = region_frames_to_region_beats (fabs ((double)distance));
3230  }
3231 
3232  if (!delta) {
3233  return;
3234  }
3235 
3236  if (!forward) {
3237  delta = -delta;
3238  }
3239 
3240  start_note_diff_command (_("nudge"));
3241 
3242  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3243  Selection::iterator next = i;
3244  ++next;
3245  change_note_time (*i, delta, true);
3246  i = next;
3247  }
3248 
3249  apply_diff ();
3250 }
3251 
3252 void
3254 {
3255  start_note_diff_command(_("change channel"));
3256  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3257  note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3258  }
3259 
3260  apply_diff();
3261 }
3262 
3263 
3264 void
3266 {
3267  Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3268 
3270  note_selected (ev, true);
3271  } else if (editor->current_mouse_mode() == MouseContent) {
3272  show_verbose_cursor (ev->note ());
3273  } else if (editor->current_mouse_mode() == MouseDraw) {
3274  show_verbose_cursor (ev->note ());
3275  }
3276 }
3277 
3278 void
3280 {
3281  Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3282 
3283  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3284  (*i)->hide_velocity ();
3285  }
3286 
3287  editor->verbose_cursor()->hide ();
3288 }
3289 
3290 void
3292 {
3293  ostringstream s;
3294  /* XXX should get patch name if we can */
3295  s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3296  << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3297  << _("Channel ") << ((int) p->patch()->channel() + 1);
3298  show_verbose_cursor (s.str(), 10, 20);
3299  p->item().grab_focus();
3300 }
3301 
3302 void
3304 {
3306  /* focus will transfer back via the enter-notify event sent to this
3307  * midi region view.
3308  */
3309 }
3310 
3311 void
3313 {
3314  ostringstream s;
3315  // CAIROCANVAS
3316  // need a way to extract text from p->_flag->_text
3317  // s << p->text();
3318  // show_verbose_cursor (s.str(), 10, 20);
3319  p->item().grab_focus();
3320 }
3321 
3322 void
3324 {
3326  /* focus will transfer back via the enter-notify event sent to this
3327  * midi region view.
3328  */
3329 }
3330 
3331 void
3332 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3333 {
3334  Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3335  Editing::MouseMode mm = editor->current_mouse_mode();
3336  bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3337 
3339  if (can_set_cursor && ctx) {
3340  if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3341  ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3342  } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3343  ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3344  } else {
3345  ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3346  }
3347  }
3348 }
3349 
3350 uint32_t
3352 {
3353  const std::string mod_name = (_dragging ? "dragging region" :
3354  trackview.editor().internal_editing() ? "editable region" :
3355  "midi frame base");
3356  if (_selected) {
3357  return ARDOUR_UI::config()->color_mod ("selected region base", mod_name);
3358  } else if ((!ARDOUR_UI::config()->get_show_name_highlight() || high_enough_for_name) &&
3359  !ARDOUR_UI::config()->get_color_regions_using_track_color()) {
3360  return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name);
3361  }
3362  return ARDOUR_UI::config()->color_mod (fill_color, mod_name);
3363 }
3364 
3365 void
3367 {
3368  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3369  uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3371 
3372  if (mode == ForceChannel) {
3373  mask = 0xFFFF; // Show all notes as active (below)
3374  }
3375 
3376  // Update notes for selection
3377  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3378  (*i)->on_channel_selection_change (mask);
3379  }
3380 
3381  _patch_changes.clear ();
3383 }
3384 
3385 void
3387 {
3388  redisplay_model();
3389 }
3390 
3391 void
3393 {
3394  if (_selection.empty()) {
3395  return;
3396  }
3397 
3399 
3400  switch (op) {
3401  case Delete:
3402  /* XXX what to do ? */
3403  break;
3404  case Cut:
3405  case Copy:
3406  editor.get_cut_buffer().add (selection_as_cut_buffer());
3407  break;
3408  default:
3409  break;
3410  }
3411 
3412  if (op != Copy) {
3413 
3415 
3416  for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3417  switch (op) {
3418  case Copy:
3419  break;
3420  case Delete:
3421  case Cut:
3422  case Clear:
3423  note_diff_remove_note (*i);
3424  break;
3425  }
3426  }
3427 
3428  apply_diff();
3429  }
3430 }
3431 
3434 {
3435  Notes notes;
3436 
3437  for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3438  NoteType* n = (*i)->note().get();
3439  notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3440  }
3441 
3443  cb->set (notes);
3444 
3445  return cb;
3446 }
3447 
3449 bool
3450 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3451 {
3452  bool commit = false;
3453  // Paste notes, if available
3454  MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3455  if (m != selection.midi_notes.end()) {
3456  ctx.counts.increase_n_notes();
3457  if (!(*m)->empty()) { commit = true; }
3458  paste_internal(pos, ctx.count, ctx.times, **m);
3459  }
3460 
3461  // Paste control points to automation children, if available
3462  typedef RouteTimeAxisView::AutomationTracks ATracks;
3463  const ATracks& atracks = midi_view()->automation_tracks();
3464  for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3465  if (a->second->paste(pos, selection, ctx)) {
3466  commit = true;
3467  }
3468  }
3469 
3470  if (commit) {
3472  }
3473  return true;
3474 }
3475 
3477 void
3478 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3479 {
3480  if (mcb.empty()) {
3481  return;
3482  }
3483 
3484  start_note_diff_command (_("paste"));
3485 
3486  const Evoral::Beats snap_beats = get_grid_beats(pos);
3487  const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3488  const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3489  const Evoral::Beats duration = last_time - first_time;
3490  const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3491  const Evoral::Beats paste_offset = snap_duration * paste_count;
3492  const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3493  Evoral::Beats end_point = Evoral::Beats();
3494 
3495  DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3496  first_time,
3497  last_time,
3498  duration, pos, _region->position(),
3499  pos_beats));
3500 
3501  clear_selection ();
3502 
3503  for (int n = 0; n < (int) times; ++n) {
3504 
3505  for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3506 
3507  boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3508  copied_note->set_time (pos_beats + copied_note->time() - first_time);
3509 
3510  /* make all newly added notes selected */
3511 
3512  note_diff_add_note (copied_note, true);
3513  end_point = copied_note->end_time();
3514  }
3515  }
3516 
3517  /* if we pasted past the current end of the region, extend the region */
3518 
3519  framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3520  framepos_t region_end = _region->position() + _region->length() - 1;
3521 
3522  if (end_frame > region_end) {
3523 
3524  DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3525 
3526  _region->clear_changes ();
3527  _region->set_length (end_frame - _region->position());
3529  }
3530 
3531  apply_diff (true);
3532 }
3533 
3536  return a->note()->time() < b->note()->time();
3537  }
3538 };
3539 
3540 void
3542 {
3543  if (!_sort_needed) {
3544  return;
3545  }
3546 
3548  _events.sort (cmp);
3549 
3550  _sort_needed = false;
3551 }
3552 
3553 void
3554 MidiRegionView::goto_next_note (bool add_to_selection)
3555 {
3556  bool use_next = false;
3557 
3558  if (_events.back()->selected()) {
3559  return;
3560  }
3561 
3562  time_sort_events ();
3563 
3564  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3565  uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3566 
3567  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3568  if ((*i)->selected()) {
3569  use_next = true;
3570  continue;
3571  } else if (use_next) {
3572  if (channel_mask & (1 << (*i)->note()->channel())) {
3573  if (!add_to_selection) {
3574  unique_select (*i);
3575  } else {
3576  note_selected (*i, true, false);
3577  }
3578  return;
3579  }
3580  }
3581  }
3582 
3583  /* use the first one */
3584 
3585  if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3586  unique_select (_events.front());
3587  }
3588 }
3589 
3590 void
3591 MidiRegionView::goto_previous_note (bool add_to_selection)
3592 {
3593  bool use_next = false;
3594 
3595  if (_events.front()->selected()) {
3596  return;
3597  }
3598 
3599  time_sort_events ();
3600 
3601  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3602  uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3603 
3604  for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3605  if ((*i)->selected()) {
3606  use_next = true;
3607  continue;
3608  } else if (use_next) {
3609  if (channel_mask & (1 << (*i)->note()->channel())) {
3610  if (!add_to_selection) {
3611  unique_select (*i);
3612  } else {
3613  note_selected (*i, true, false);
3614  }
3615  return;
3616  }
3617  }
3618  }
3619 
3620  /* use the last one */
3621 
3622  if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3623  unique_select (*(_events.rbegin()));
3624  }
3625 }
3626 
3627 void
3628 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3629 {
3630  bool had_selected = false;
3631 
3632  time_sort_events ();
3633 
3634  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3635  if ((*i)->selected()) {
3636  selected.insert ((*i)->note());
3637  had_selected = true;
3638  }
3639  }
3640 
3641  if (allow_all_if_none_selected && !had_selected) {
3642  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3643  selected.insert ((*i)->note());
3644  }
3645  }
3646 }
3647 
3648 void
3650 {
3651  x = std::max(0.0, x);
3652 
3653  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3654 
3655  _last_ghost_x = x;
3656  _last_ghost_y = y;
3657 
3658  _note_group->canvas_to_item (x, y);
3659 
3661 
3662  framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3663  framecnt_t grid_frames;
3664  framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3665 
3666  /* calculate time in beats relative to start of source */
3667  const Evoral::Beats length = get_grid_beats(unsnapped_frame);
3668  const Evoral::Beats time = std::max(
3669  Evoral::Beats(),
3671 
3672  _ghost_note->note()->set_time (time);
3673  _ghost_note->note()->set_length (length);
3674  _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3675  _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3676  _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3677 
3678  /* the ghost note does not appear in ghost regions, so pass false in here */
3679  update_note (_ghost_note, false);
3680 
3682 }
3683 
3684 void
3686 {
3687  remove_ghost_note ();
3688 
3690  if (midi_view()->note_mode() == Sustained) {
3691  _ghost_note = new Note (*this, _note_group, g);
3692  } else {
3693  _ghost_note = new Hit (*this, _note_group, 10, g);
3694  }
3696  _ghost_note->set_outline_color (0x000000aa);
3697  update_ghost_note (x, y);
3698  _ghost_note->show ();
3699 
3701 }
3702 
3703 void
3705 {
3706  delete _ghost_note;
3707  _ghost_note = 0;
3708 }
3709 
3710 void
3712 {
3713  if (!_ghost_note) {
3714  return;
3715  }
3716 
3718 }
3719 
3720 void
3722 {
3723  _mouse_state = None;
3724 }
3725 
3726 void
3727 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3728 {
3729  /* XXX: This is dead code. What was it for? */
3730 
3731  double note = midi_stream_view()->y_to_note(y);
3732  Events e;
3733  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3734 
3735  uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3736 
3737  if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3738  get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3739  } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3740  get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3741  } else {
3742  return;
3743  }
3744 
3745  bool add_mrv_selection = false;
3746 
3747  if (_selection.empty()) {
3748  add_mrv_selection = true;
3749  }
3750 
3751  for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3752  if (_selection.insert (*i).second) {
3753  (*i)->set_selected (true);
3754  }
3755  }
3756 
3757  if (add_mrv_selection) {
3759  editor.get_selection().add (this);
3760  }
3761 }
3762 
3763 void
3765 {
3767 
3768  for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3769  (*i)->set_selected ((*i)->selected()); // will change color
3770  }
3771 
3772  /* XXX probably more to do here */
3773 }
3774 
3775 void
3777 {
3779  if (yn) {
3780  redisplay_model ();
3781  }
3782 }
3783 
3784 void
3786 {
3787  if (_step_edit_cursor == 0) {
3788  ArdourCanvas::Item* const group = get_canvas_group();
3789 
3790  _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3791  _step_edit_cursor->set_y0 (0);
3792  _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3793  _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3794  _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3795  }
3796 
3797  move_step_edit_cursor (pos);
3798  _step_edit_cursor->show ();
3799 }
3800 
3801 void
3803 {
3805 
3806  if (_step_edit_cursor) {
3808  _step_edit_cursor->set_x0 (pixel);
3810  }
3811 }
3812 
3813 void
3815 {
3816  if (_step_edit_cursor) {
3817  _step_edit_cursor->hide ();
3818  }
3819 }
3820 
3821 void
3823 {
3824  _step_edit_cursor_width = beats;
3825 
3826  if (_step_edit_cursor) {
3828  }
3829 }
3830 
3834 void
3836 {
3837  if (!_active_notes) {
3838  /* we aren't actively being recorded to */
3839  return;
3840  }
3841 
3842  boost::shared_ptr<MidiSource> src = w.lock ();
3843  if (!src || src != midi_region()->midi_source()) {
3844  /* recorded data was not destined for our source */
3845  return;
3846  }
3847 
3848  MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3849 
3851 
3852  framepos_t back = max_framepos;
3853 
3854  for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3855  Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3856 
3857  if (ev.is_channel_event()) {
3858  if (get_channel_mode() == FilterChannels) {
3859  if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3860  continue;
3861  }
3862  }
3863  }
3864 
3865  /* convert from session frames to source beats */
3867  ev.time() - src->timeline_position() + _region->start());
3868 
3869  if (ev.type() == MIDI_CMD_NOTE_ON) {
3871  new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3872 
3873  add_note (note, true);
3874 
3875  /* fix up our note range */
3876  if (ev.note() < _current_range_min) {
3878  } else if (ev.note() > _current_range_max) {
3880  }
3881 
3882  } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3883  resolve_note (ev.note (), time_beats);
3884  }
3885 
3886  back = ev.time ();
3887  }
3888 
3890 }
3891 
3892 void
3894 {
3895  /* Reparent the note group to the region view's parent, so that it doesn't change
3896  when the region view is trimmed.
3897  */
3898  _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3899  _temporary_note_group->move (group->position ());
3900  _note_group->reparent (_temporary_note_group);
3901 }
3902 
3903 void
3905 {
3906  _note_group->reparent (group);
3907  delete _temporary_note_group;
3909 
3910  if (_region->start() < 0) {
3911  /* Trim drag made start time -ve; fix this */
3913  }
3914 }
3915 
3916 void
3918 {
3919  PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3920 
3921  int response = d.run();
3922 
3923  switch (response) {
3924  case Gtk::RESPONSE_ACCEPT:
3925  break;
3926  case Gtk::RESPONSE_REJECT:
3927  delete_patch_change (pc);
3928  return;
3929  default:
3930  return;
3931  }
3932 
3933  change_patch_change (pc->patch(), d.patch ());
3934 }
3935 
3936 void
3938 {
3939  // CAIROCANVAS
3940  // sysyex object doesn't have a pointer to a sysex event
3941  // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3942  // c->remove (sysex->sysex());
3943  // _model->apply_command (*trackview.session(), c);
3944 
3945  //_sys_exes.clear ();
3946  // display_sysexes();
3947 }
3948 
3949 void
3951 {
3952  using namespace MIDI::Name;
3953 
3954  std::string name;
3955 
3956  MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3957  if (mtv) {
3959  if (device_names) {
3960  MIDI::Name::PatchPrimaryKey patch_key;
3961  get_patch_key_at(n->time(), n->channel(), patch_key);
3962  name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
3963  n->channel(),
3964  patch_key.bank(),
3965  patch_key.program(),
3966  n->note());
3967  }
3968  }
3969 
3970  char buf[128];
3971  snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
3972  (int) n->note (),
3973  name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
3974  (int) n->channel() + 1,
3975  (int) n->velocity());
3976 
3977  show_verbose_cursor(buf, 10, 20);
3978 }
3979 
3980 void
3981 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3982 {
3983  trackview.editor().verbose_cursor()->set (text);
3985  trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
3986 }
3987 
3988 uint8_t
3990 {
3991  if (_model->notes().empty()) {
3992  return 0x40; // No notes, use default
3993  }
3994 
3995  MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
3996  if (m == _model->notes().begin()) {
3997  // Before the start, use the velocity of the first note
3998  return (*m)->velocity();
3999  } else if (m == _model->notes().end()) {
4000  // Past the end, use the velocity of the last note
4001  --m;
4002  return (*m)->velocity();
4003  }
4004 
4005  // Interpolate velocity of surrounding notes
4006  MidiModel::Notes::const_iterator n = m;
4007  --n;
4008 
4009  const double frac = ((time - (*n)->time()).to_double() /
4010  ((*m)->time() - (*n)->time()).to_double());
4011 
4012  return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4013 }
4014 
4019 framepos_t
4021 {
4023 
4024  const Evoral::Beats grid_beats = get_grid_beats(p);
4025 
4026  grid_frames = region_beats_to_region_frames (grid_beats);
4027 
4028  /* Hack so that we always snap to the note that we are over, instead of snapping
4029  to the next one if we're more than halfway through the one we're over.
4030  */
4031  if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
4032  p -= grid_frames / 2;
4033  }
4034 
4035  return snap_frame_to_frame (p);
4036 }
4037 
4041 void
4043 {
4044  if (rv == this) {
4045  return;
4046  }
4047 
4048  /* Clear our selection in sympathy; but don't signal the fact */
4049  clear_selection (false);
4050 }
4051 
4054 {
4055  RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4056  return rtav->midi_track()->get_playback_channel_mode();
4057 }
4058 
4059 uint16_t
4061 {
4062  RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4063  return rtav->midi_track()->get_playback_channel_mask();
4064 }
4065 
4066 
4069 {
4071  bool success = false;
4072  Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos);
4073  if (!success) {
4074  beats = Evoral::Beats(1);
4075  }
4076  return beats;
4077 }
void change(Gdk::Cursor *cursor)
std::set< boost::shared_ptr< NoteType > > _pending_note_selection
LIBEVORAL_API std::string midi_note_name(uint8_t noteval)
Definition: midi_util.cpp:25
MidiTimeAxisView * midi_view() const
PBD::ScopedConnection _clear_midi_selection_connection
void note_mouse_position(float xfraction, float yfraction, bool can_set_cursor=true)
static sigc::signal< void > DropDownKeys
void sysex_entered(SysEx *p)
NoteBase * _ghost_note
void delete_sysex(SysEx *)
void set_duration(double units)
Definition: ghostregion.cc:77
SysExes & sysexes()
Definition: Sequence.hpp:198
void patch_left(PatchChange *)
#define MIDI_BP_ZERO
void add_note(NoteBase *)
Definition: ghostregion.cc:307
ArdourCanvas::Item * get_canvas_group()
PBD::ScopedConnection content_connection
void delete_patch_change(PatchChange *)
ARDOUR::MidiModel::NoteDiffCommand * _note_diff_command
void apply_command(Session &session, Command *cmd)
Definition: midi_model.cc:97
void remove_from_selection(NoteBase *)
MIDI::Name::PatchPrimaryKey patch_change_to_patch_key(ARDOUR::MidiModel::PatchChangePtr)
void remove_note(NoteBase *)
Definition: ghostregion.cc:384
void midi_channel_mode_changed()
virtual ArdourCanvas::Coord x1() const =0
Force all events to a certain channel.
Definition: types.h:212
static PBD::Signal1< void, RegionView * > RegionViewGoingAway
Definition: region_view.h:100
void get_patch_key_at(Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey &key) const
#define MIDI_CMD_NOTE_OFF
Definition: midi_events.h:107
boost::shared_ptr< ARDOUR::MidiModel > _model
boost::shared_ptr< ARDOUR::MidiTrack > midi_track() const
Definition: route_ui.cc:1762
void show()
Definition: sys_ex.cc:82
bool leave_notify(GdkEventCrossing *)
void set_y0(ArdourCanvas::Coord)
Definition: note.cc:107
void set_offset(ArdourCanvas::Duple const &)
void set_y1(ArdourCanvas::Coord)
Definition: note.cc:119
#define MIDI_CMD_NOTE_ON
Definition: midi_events.h:108
virtual void region_resized(const PBD::PropertyChange &)
Definition: region_view.cc:405
PublicEditor & editor() const
void note_left(NoteBase *ev)
static uint32_t calculate_outline(uint32_t color, bool selected=false)
calculate outline colors from fill colors of notes
Definition: note_base.h:123
MouseMode
Definition: editing.h:91
void get_events(Events &e, Evoral::Sequence< Evoral::Beats >::NoteOperator op, uint8_t val, int chan_mask=0)
PBD::Signal0< void > ChannelModeChanged
bool in_destructor
Definition: region_view.h:172
void change_channel(PatchChangePtr, uint8_t)
Definition: midi_model.cc:998
uint8_t velocity() const
Definition: MIDIEvent.hpp:78
void update_drag_selection(framepos_t start, framepos_t end, double y0, double y1, bool extend)
static void clamp_to_0_127(uint8_t &val)
Definition: midi_util.h:23
boost::shared_ptr< ARDOUR::Region > _region
Definition: region_view.h:159
bool operator()(NoteBase *a, NoteBase *b)
PatchChanges _patch_changes
virtual Editing::MouseMode current_mouse_mode() const =0
uint8_t _current_range_max
LIBEVORAL_API const Beats MaxBeats
Definition: types.cpp:26
uint8_t channel() const
Definition: MIDIEvent.hpp:64
std::string gui_property(const std::string &property_name) const
Definition: axis_view.cc:67
uint8_t note() const
Definition: MIDIEvent.hpp:76
Definition: note.h:32
LIBEVORAL_API uint64_t Note
Definition: debug.cpp:4
Events::iterator _optimization_iterator
static PBD::Signal1< void, NoteBase * > NoteBaseDeleted
Definition: note_base.h:65
virtual ArdourCanvas::Coord x0() const =0
void clear_selection_except(NoteBase *ev, bool signal=true)
Time time() const
Definition: Note.hpp:60
void trim_note(NoteBase *ev, ARDOUR::MidiModel::TimeType start_delta, ARDOUR::MidiModel::TimeType end_delta)
void change_note_lengths(bool, bool, Evoral::Beats beats, bool start, bool end)
void update_ghost_note(double, double)
std::string get_patch_name(uint16_t bank, uint8_t program, uint8_t channel) const
bool is_channel_event() const
Definition: MIDIEvent.hpp:97
ARDOUR::BeatsFramesConverter _region_relative_time_converter
shared_ptr< T > dynamic_pointer_cast(shared_ptr< U > const &r)
Definition: shared_ptr.hpp:396
void update_sustained(Note *, bool update_ghost_regions=true)
void selection_cleared(MidiRegionView *)
bool scroll(GdkEventScroll *)
void change_time(PatchChangePtr, TimeType)
Definition: midi_model.cc:986
virtual void color_handler()
Definition: region_view.h:157
void clear()
Definition: selection.cc:101
void update_note(NoteBase *)
Definition: ghostregion.cc:355
bool _enable_display
see StreamView::redisplay_diskstream()
Definition: region_view.h:170
void create_note_at(framepos_t t, double y, Evoral::Beats length, bool snap_t)
void change_note_channel(NoteBase *, int8_t, bool relative=false)
MidiRegionView(ArdourCanvas::Container *parent, RouteTimeAxisView &tv, boost::shared_ptr< ARDOUR::MidiRegion > r, double samples_per_pixel, uint32_t basic_color)
void add_command(Command *const cmd)
Definition: session.h:787
framepos_t source_beats_to_region_frames(Evoral::Beats beats) const
static bool patch_applies(const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
Return true iff pc applies to the given time on the given channel.
TempoMap & tempo_map()
Definition: session.h:596
void validate()
Definition: note_base.cc:88
uint8_t note() const
Definition: Note.hpp:62
Evoral::Beats _step_edit_cursor_width
Always round up, even if on a division.
Definition: types.h:225
virtual void set_outline_color(uint32_t c)=0
PBD::Signal0< void > Changed
Representation of the interface of the Editor class.
void create_ghost_note(double, double)
TimeAxisView & trackview
void set_fill_color(uint32_t)
Definition: hit.cc:62
std::string fill_color_name
boost::shared_ptr< MidiBuffer > get_gui_feed_buffer() const
Definition: midi_track.cc:867
tuple f
Definition: signals.py:35
Evoral::Sequence< Evoral::Beats >::Notes Notes
Definition: Beats.hpp:239
framecnt_t _last_display_zoom
void sysex_left(SysEx *p)
virtual std::string get_item_name() const
virtual double sample_to_pixel(framepos_t frame) const =0
void goto_next_note(bool add_to_selection)
void set_outline_all()
Definition: note.cc:131
ArdourCanvas::Text * name_text
ARDOUR::InstrumentInfo & instrument_info() const
virtual void show()=0
Gdk::Cursor * grabber_note
Definition: mouse_cursors.h:49
const AutomationTracks & automation_tracks() const
void step_sustain(Evoral::Beats beats)
void set_note(uint8_t n)
Definition: Note.hpp:81
bool key_release(GdkEventKey *)
uint16_t get_playback_channel_mask() const
Definition: midi_track.h:119
ChannelMode
Definition: types.h:209
PBD::Signal0< void > ContentsChanged
Definition: midi_model.h:274
void hide()
Definition: hit.cc:74
Evoral::Beats get_grid_beats(framepos_t pos) const
MouseState _mouse_state
void unique_select(NoteBase *ev)
void change_bank(PatchChangePtr, int)
Definition: midi_model.cc:1024
void add(boost::shared_ptr< NoteType >)
Definition: note_player.cc:40
void note_entered(NoteBase *ev)
size_t n_notes() const
Definition: item_counts.h:43
Evoral::Beats region_frames_to_region_beats(framepos_t) const
LIBARDOUR_API PBD::PropertyDescriptor< framepos_t > start
Definition: region.cc:63
bool enter_notify(GdkEventCrossing *)
std::vector< NoteResizeData * > _resize_data
void transpose(bool up, bool fine, bool allow_smush)
uint8_t program() const
Definition: PatchChange.hpp:95
uint8_t channel() const
uint32_t current_height() const
framecnt_t frame_rate() const
Definition: session.h:365
virtual Evoral::Beats get_grid_type_as_beats(bool &success, framepos_t position)=0
double y_position() const
MidiChannelFilter & playback_filter()
Definition: midi_track.h:122
#define invalidator(x)
Definition: gui_thread.h:40
bool empty(bool internal_selection=false)
Definition: selection.cc:938
uint8_t lowest_note() const
ArdourCanvas::Item & item() const
Definition: sys_ex.h:45
void cut_copy_clear(Editing::CutCopyOp)
framepos_t source_beats_to_absolute_frames(Evoral::Beats beats) const
ArdourCanvas::Rectangle * resize_rect
#define UINT_RGBA_CHANGE_A(x, a)
Definition: rgb_macros.h:64
virtual VerboseCursor * verbose_cursor() const =0
void change_note_velocity(NoteBase *ev, int8_t vel, bool relative=false)
const Meter & meter_at(framepos_t) const
Definition: tempo.cc:1652
MidiStreamView * midi_stream_view() const
static Handle create(Editor &editor, Gdk::Cursor *cursor)
void reset_width_dependent_items(double pixel_width)
MidiModel::NoteDiffCommand * new_note_diff_command(const std::string name="midi edit")
Definition: midi_model.cc:62
void change_patch_change(PatchChange &old_patch, const MIDI::Name::PatchPrimaryKey &new_patch)
framepos_t snap_pixel_to_sample(double x)
NoteBase * add_note(const boost::shared_ptr< NoteType > note, bool visible)
#define _(Text)
Definition: i18n.h:11
void note_selected(NoteBase *ev, bool add, bool extend=false)
uint16_t get_selected_channels() const
PatchChanges & patch_changes()
Definition: Sequence.hpp:211
virtual void enable_display(bool yn)
Definition: region_view.h:97
uint8_t get_channel_for_add() const
void add(std::list< Selectable * > const &)
Definition: selection.cc:1045
virtual void snap_to(framepos_t &first, ARDOUR::RoundMode direction=ARDOUR::RoundNearest, bool for_mark=false)=0
MidiListEditor * _list_editor
bool motion(GdkEventMotion *)
LIBGTKMM2EXT_API uint64_t Keyboard
Definition: debug.cc:23
void start_playing_midi_note(boost::shared_ptr< NoteType > note)
void select_range(framepos_t start, framepos_t end)
double _pixel_width
Definition: region_view.h:171
virtual void hide()=0
bool canvas_group_event(GdkEvent *)
Definition: region_view.cc:217
void update_hit(Hit *, bool update_ghost_regions=true)
#define X_(Text)
Definition: i18n.h:13
void set_outline_what(ArdourCanvas::Rectangle::What)
Definition: note.cc:125
int64_t framecnt_t
Definition: types.h:76
virtual DragManager * drags() const =0
LIBARDOUR_API RCConfiguration * Config
Definition: globals.cc:119
Gdk::Cursor * midi_pencil
Definition: mouse_cursors.h:56
ARDOUR::ChannelMode get_channel_mode() const
Definition: hit.h:30
void change_note_time(NoteBase *ev, ARDOUR::MidiModel::TimeType, bool relative=false)
virtual Editing::SnapMode snap_mode() const =0
boost::shared_ptr< ARDOUR::Region > region() const
Definition: region_view.h:66
Definition: sys_ex.h:29
void show_step_edit_cursor(Evoral::Beats pos)
void show_verbose_cursor(std::string const &, double, double) const
uint64_t to_ticks() const
Definition: Beats.hpp:186
uint8_t channel() const
Definition: Note.hpp:66
static Beats tick()
Definition: Beats.hpp:196
uint32_t base_color()
Definition: note_base.cc:159
virtual framecnt_t get_current_zoom() const =0
void note_deselected(NoteBase *ev)
const boost::shared_ptr< ARDOUR::MidiRegion > midi_region() const
void get_notes(Notes &, NoteOperator, uint8_t val, int chan_mask=0) const
Definition: Sequence.cpp:1245
void selection_as_notelist(Notes &selected, bool allow_all_if_none_selected=false)
void toggle_matching_notes(uint8_t notenum, uint16_t channel_mask)
Editing::MouseMode current_mouse_mode() const
Definition: editor.h:179
virtual void init(bool wait_for_data)
Definition: region_view.cc:149
void set_position(ArdourCanvas::Duple)
Definition: hit.cc:108
void check_record_layers(boost::shared_ptr< ARDOUR::Region >, ARDOUR::framepos_t)
Definition: streamview.cc:688
void show()
Definition: hit.cc:68
void delete_note(boost::shared_ptr< NoteType >)
void display_model(boost::shared_ptr< ARDOUR::MidiModel > model)
boost::shared_ptr< CursorContext > _press_cursor_ctx
void set_fill_color(uint32_t)
Definition: note.cc:83
framepos_t region_beats_to_region_frames(Evoral::Beats beats) const
bool note_in_region_range(const boost::shared_ptr< NoteType > note, bool &visible) const
Definition: amp.h:29
ARDOUR::frameoffset_t snap_frame_to_frame(ARDOUR::frameoffset_t) const
Definition: region_view.cc:945
void parameter_changed(std::string const &p)
framecnt_t get_duration() const
ARDOUR::Session * session() const
Definition: axis_view.h:52
ARDOUR::BeatsFramesConverter _source_relative_time_converter
virtual void begin_reversible_command(std::string cmd_name)=0
#define gui_context()
Definition: gui_thread.h:36
virtual void set_frame_color()
void clear_points()
Definition: selection.cc:1078
void update_vertical_drag_selection(double last_y, double y, bool extend)
virtual void commit_reversible_command()=0
static uint32_t meter_style_fill_color(uint8_t vel, bool selected)
Definition: note_base.h:106
Time time() const
Definition: Event.hpp:132
void change(const NotePtr note, Property prop, uint8_t new_value)
Definition: midi_model.h:109
Selection _selection
void set(Drag *, GdkEvent *, Gdk::Cursor *c=MouseCursors::invalid_cursor())
Definition: editor_drag.cc:124
Gdk::Cursor * right_side_trim
Definition: mouse_cursors.h:39
void maybe_remove_deleted_note_from_selection(NoteBase *)
void apply_command_as_subcommand(Session &session, Command *cmd)
Definition: midi_model.cc:111
ARDOUR::MidiModel::PatchChangePtr patch() const
Definition: patch_change.h:57
void step_patch(PatchChange &patch, bool bank, int delta)
virtual ReadLock read_lock() const
Definition: Sequence.hpp:92
void swap(shared_ptr< T > &a, shared_ptr< T > &b)
Definition: shared_ptr.hpp:381
void update_note_range(uint8_t note_num)
MouseCursors const * cursors() const
Definition: editor.h:473
PBD::Signal0< void > SnapChanged
#define DEBUG_TRACE(bits, str)
Definition: debug.h:55
Evoral::Beats from(framepos_t frames) const
ArdourCanvas::Container * _note_group
int64_t framepos_t
Definition: types.h:66
bool motion_handler(GdkEvent *, bool)
Definition: editor_drag.cc:180
void set_selected(bool yn)
Definition: note_base.cc:144
PBD::ScopedConnection snap_changed_connection
ArdourCanvas::Rectangle * frame_handle_start
`frame' (fade) handle for the start of the item, or 0
virtual Selection & get_selection() const =0
VerboseCursor * verbose_cursor() const
Definition: editor.h:477
void instrument_settings_changed()
EnterContext * get_enter_context(ItemType type)
shared_ptr< T > static_pointer_cast(shared_ptr< U > const &r)
Definition: shared_ptr.hpp:386
PBD::ScopedConnection _instrument_changed_connection
void hide_velocity()
Definition: note_base.cc:113
virtual void region_muted()
Definition: region_view.cc:439
int64_t frameoffset_t
Definition: types.h:71
Beats snap_to(const Evoral::Beats &snap) const
Definition: Beats.hpp:75
MidiCutBuffer * selection_as_cut_buffer() const
void move_step_edit_cursor(Evoral::Beats pos)
double get_end_position_pixels()
void set_outline_color(uint32_t)
Definition: note.cc:77
ArdourCanvas::Coord y1() const
Definition: note.cc:71
void update_resizing(NoteBase *, bool, double, bool)
virtual void reset_width_dependent_items(double pixel_width)
Definition: region_view.cc:432
void patch_entered(PatchChange *)
PBD::Signal1< void, std::string > ParameterChanged
Definition: configuration.h:44
virtual double height() const
uint8_t _current_range_min
ArdourCanvas::Container * _temporary_note_group
void display_patch_changes_on_channel(uint8_t, bool)
void set_x0(ArdourCanvas::Coord)
Definition: note.cc:101
bool _selected
Definition: selectable.h:45
void region_resized(const PBD::PropertyChange &)
void set(const Evoral::Sequence< TimeType >::Notes &)
CutCopyOp
Definition: editing.h:198
NoteBase * find_canvas_note(boost::shared_ptr< NoteType >)
ItemCounts counts
Count of consumed selection items.
Definition: paste_context.h:37
uint8_t get_velocity_for_add(ARDOUR::MidiModel::TimeType time) const
framepos_t position() const
Definition: region.h:112
void set_step_edit_cursor_width(Evoral::Beats beats)
uint8_t type() const
Definition: MIDIEvent.hpp:60
void apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
void maybe_select_by_position(GdkEventButton *ev, double x, double y)
void remove_ghost(GhostRegion *)
Definition: region_view.cc:715
void connect_to_diskstream()
void hide()
Definition: sys_ex.cc:76
void change_note_length(NoteBase *, ARDOUR::MidiModel::TimeType)
const boost::shared_ptr< NoteType > note() const
Definition: note_base.h:103
void play_midi_note(boost::shared_ptr< NoteType > note)
uint8_t velocity() const
void clear_midi_selection()
void add_canvas_patch_change(ARDOUR::MidiModel::PatchChangePtr patch, const std::string &displaytext, bool)
PatchChanges::const_iterator patch_change_lower_bound(Time t) const
Definition: Sequence.cpp:1189
double snap_to_pixel(double x)
PBD::ScopedConnection _mouse_mode_connection
PointSelection points
Definition: selection.h:86
virtual bool set_duration(framecnt_t, void *)
Definition: region_view.cc:500
std::set< boost::shared_ptr< NoteType > > _marked_for_velocity
const char * name
Notes & notes()
Definition: Sequence.hpp:154
virtual bool internal_editing() const =0
Definition: editor.h:134
void commit_resizing(NoteBase *, bool, double, bool)
void step_add_note(uint8_t channel, uint8_t number, uint8_t velocity, Evoral::Beats pos, Evoral::Beats len)
void paste_internal(framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer &)
MidiModel::PatchChangeDiffCommand * new_patch_change_diff_command(const std::string name="midi edit")
Definition: midi_model.cc:82
Time time() const
Definition: PatchChange.hpp:76
framepos_t snap_frame_to_grid_underneath(framepos_t p, framecnt_t &) const
uint8_t active_channel() const
void change_channel(uint8_t channel)
LIBARDOUR_API PBD::PropertyDescriptor< bool > relative
Definition: route_group.cc:42
ArdourCanvas::Rectangle * frame
virtual void set_height(double)
Definition: region_view.cc:730
boost::shared_ptr< CursorContext > cursor_ctx
Definition: editor.h:489
void select_matching_notes(uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
void enable_display(bool)
PBD::Signal1< void, boost::weak_ptr< MidiSource > > DataRecorded
Definition: midi_track.h:127
void move_patch_change(PatchChange &, Evoral::Beats)
bool button_press(GdkEventButton *)
virtual void set_ignore_events(bool ignore)=0
void set_length(framecnt_t)
Definition: region.cc:430
ArdourCanvas::Color color_mod(std::string const &color, std::string const &modifier) const
Definition: ui_config.cc:555
LIBARDOUR_API PBD::PropertyDescriptor< framepos_t > position
Definition: region.cc:65
void note_diff_remove_note(NoteBase *ev)
uint64_t CutNPaste
Definition: debug.cc:29
static UIConfiguration * config()
Definition: ardour_ui.h:188
bool empty() const
Definition: Sequence.hpp:116
void start_playing_midi_chord(std::vector< boost::shared_ptr< NoteType > > notes)
std::vector< GhostRegion * > ghosts
Definition: region_view.h:176
void change_velocities(bool up, bool fine, bool allow_smush, bool all_together)
uint32_t get_fill_color() const
void move_selection(double dx, double dy, double cumulative_dy)
void update_note(NoteBase *, bool update_ghost_regions=true)
Time end_time() const
Definition: Note.hpp:61
void change_note_note(NoteBase *ev, int8_t note, bool relative=false)
ArdourCanvas::Coord x0() const
Definition: note.cc:53
bool key_press(GdkEventKey *)
void set_note_range(VisibleNoteRange r)
uint8_t highest_note() const
LIBARDOUR_API PBD::PropertyDescriptor< bool > select
Definition: route_group.cc:48
Definition: debug.h:30
void change_program(PatchChangePtr, uint8_t)
Definition: midi_model.cc:1011
uint8_t y_to_note(double y) const
void region_locked()
Definition: region_view.cc:398
double divisions_per_bar() const
Definition: tempo.h:70
GhostRegion * add_ghost(TimeAxisView &)
double to_double() const
Definition: Beats.hpp:185
framepos_t timeline_position() const
Definition: source.h:100
ArdourCanvas::Rectangle * frame_handle_end
`frame' (fade) handle for the end of the item, or 0
float times
Number of times to paste.
Definition: paste_context.h:36
void remove(const NotePtr note)
Definition: midi_model.cc:155
virtual framepos_t pixel_to_sample(double pixel) const =0
ArdourCanvas::Item & item() const
Definition: patch_change.h:59
bool button_release(GdkEventButton *)
bool contains(const NotePtr &ev) const
Definition: Sequence.cpp:1103
std::set< boost::shared_ptr< NoteType > > _marked_for_selection
void set_x1(ArdourCanvas::Coord)
Definition: note.cc:113
bool paste(framepos_t pos, const ::Selection &selection, PasteContext &ctx)
LIBEVORAL_API uint64_t Beats
ArdourCanvas::Container * group
Time length() const
Definition: Note.hpp:65
void set_height(double)
bool contains(PropertyDescriptor< T > p) const
void set_outline_color(uint32_t)
Definition: hit.cc:56
PBD::ScopedConnection note_delete_connection
std::map< Evoral::Parameter, boost::shared_ptr< AutomationTimeAxisView > > AutomationTracks
void note_dropped(NoteBase *ev, ARDOUR::frameoffset_t, int8_t d_note)
framecnt_t length() const
Definition: region.h:114
static const framepos_t max_framepos
Definition: types.h:78
bool selected() const
Definition: note_base.h:74
void region_sync_changed()
Definition: region_view.cc:606
InstrumentInfo & instrument_info()
Definition: route.h:440
void add_to_selection(NoteBase *)
void goto_previous_note(bool add_to_selection)
static PBD::Signal1< void, MidiRegionView * > SelectionCleared
iterator begin()
Definition: midi_buffer.h:127
static double note_height(TimeAxisView &trackview, MidiStreamView *mv)
Definition: ghostregion.cc:254
boost::shared_ptr< MidiSource > midi_source(uint32_t n=0) const
Definition: midi_region.cc:390
ChannelMode get_playback_channel_mode() const
Definition: midi_track.h:117
ArdourCanvas::Coord x1() const
Definition: note.cc:59
void set(std::string const &)
framepos_t start() const
Definition: region.h:113
#define UINT_INTERPOLATE(c1, c2, t)
Definition: rgb_macros.h:70
void note_diff_add_change(NoteBase *ev, ARDOUR::MidiModel::NoteDiffCommand::Property, uint8_t val)
ArdourCanvas::Coord y0() const
Definition: note.cc:65
void apply_diff(bool as_subcommand=false)
#define RGBA_TO_UINT(r, g, b, a)
Definition: rgb_macros.h:34
LIBGTKMM2EXT_API int pixel_width(const std::string &str, Pango::FontDescription &font)
void select_notes(std::list< boost::shared_ptr< NoteType > >)
framepos_t to(Evoral::Beats beats) const
virtual void begin_reversible_selection_op(std::string cmd_name)=0
void resolve_note(uint8_t note_num, Evoral::Beats end_time)
void increase_n_notes(size_t delta=1)
Definition: item_counts.h:57
void nudge_notes(bool forward, bool fine)
void start_note_diff_command(std::string name="midi edit")
Gdk::Cursor * left_side_trim
Definition: mouse_cursors.h:41
boost::shared_ptr< MIDI::Name::MasterDeviceNames > get_device_names()
double contents_height() const
unsigned count
Number of previous pastes to the same position.
Definition: paste_context.h:35
void clear_events(bool with_selection_signal=true)
Notes::const_iterator note_lower_bound(Time t) const
Definition: Sequence.cpp:1178
framepos_t get_position() const
#define MISSING_INVALIDATOR
Definition: event_loop.h:86
double get_position_pixels()
Evoral::Beats _step_edit_cursor_position
void begin_resizing(bool at_front)
static PBD::Signal1< void, GhostRegion * > CatchDeletion
Definition: ghostregion.h:58
void data_recorded(boost::weak_ptr< ARDOUR::MidiSource >)
std::list< NoteBase * > Events
PBD::Signal0< void > MouseModeChanged
void clear_changes()
Definition: stateful.cc:184
void set_height(ArdourCanvas::Coord)
Definition: hit.cc:96
void edit_patch_change(PatchChange *)
PBD::ScopedConnection _selection_cleared_connection
boost::shared_ptr< ARDOUR::Route > route() const
Definition: route_ui.h:76
MidiStreamView * midi_view()
virtual void set_colors()
Definition: region_view.cc:514
void play()
Definition: note_player.cc:61
Always round down, even if on a division.
Definition: types.h:223
void note_diff_add_note(const boost::shared_ptr< NoteType > note, bool selected, bool show_velocity=false)
bool canvas_group_event(GdkEvent *ev)
PBD::ScopedConnection _channel_mode_changed_connection
boost::shared_ptr< MidiPlaylist > midi_playlist()
Definition: midi_track.cc:808
bool end_grab(GdkEvent *)
Definition: editor_drag.cc:149
void add(const NotePtr note)
Definition: midi_model.cc:148
LIBARDOUR_API PBD::PropertyChange bounds_change
Definition: globals.cc:150
std::string string_compose(const std::string &fmt, const T1 &o1)
Definition: compose.h:208
RegionEditor * editor
Definition: region_view.h:164
void add_patch_change(framecnt_t, Evoral::PatchChange< Evoral::Beats > const &)
Ignore events on certain channels.
Definition: types.h:211
virtual void commit_reversible_selection_op()=0
Evoral::Beats absolute_frames_to_source_beats(framepos_t) const
void clear_selection(bool signal=true)
framepos_t last_frame() const
Definition: region.h:142
virtual framecnt_t get_nudge_distance(framepos_t pos, framecnt_t &next)=0
LIBARDOUR_API PBD::PropertyDescriptor< framecnt_t > length
Definition: region.cc:64
ArdourCanvas::Rectangle * _step_edit_cursor
def signal
Definition: signals.py:53
LIBARDOUR_API PBD::PropertyDescriptor< bool > color
Definition: route_group.cc:50
void apply_note_range(uint8_t lowest, uint8_t highest, bool force=false)
void init(bool wfd)