2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas_patch_change.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "note_player.h"
65 #include "public_editor.h"
66 #include "rgb_macros.h"
67 #include "selection.h"
68 #include "simpleline.h"
69 #include "streamview.h"
71 #include "mouse_cursors.h"
72 #include "patch_change_dialog.h"
73 #include "verbose_cursor.h"
77 using namespace ARDOUR
;
79 using namespace Editing
;
80 using namespace ArdourCanvas
;
81 using Gtkmm2ext::Keyboard
;
83 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
85 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
86 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
const & basic_color
)
87 : RegionView (parent
, tv
, r
, spu
, basic_color
)
89 , _last_channel_selection(0xFFFF)
90 , _current_range_min(0)
91 , _current_range_max(0)
92 , _model_name(string())
93 , _custom_device_mode(string())
95 , _note_group(new ArdourCanvas::Group(*group
))
96 , _note_diff_command (0)
99 , _step_edit_cursor (0)
100 , _step_edit_cursor_width (1.0)
101 , _step_edit_cursor_position (0.0)
102 , _channel_selection_scoped_note (0)
103 , _temporary_note_group (0)
106 , _sort_needed (true)
107 , _optimization_iterator (_events
.end())
109 , _no_sound_notes (false)
112 , _pre_enter_cursor (0)
114 _note_group
->raise_to_top();
115 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
117 Config
->ParameterChanged
.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed
, this, _1
), gui_context());
118 connect_to_diskstream ();
121 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
122 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
& basic_color
,
123 TimeAxisViewItem::Visibility visibility
)
124 : RegionView (parent
, tv
, r
, spu
, basic_color
, false, visibility
)
126 , _last_channel_selection(0xFFFF)
127 , _model_name(string())
128 , _custom_device_mode(string())
130 , _note_group(new ArdourCanvas::Group(*parent
))
131 , _note_diff_command (0)
134 , _step_edit_cursor (0)
135 , _step_edit_cursor_width (1.0)
136 , _step_edit_cursor_position (0.0)
137 , _channel_selection_scoped_note (0)
138 , _temporary_note_group (0)
141 , _sort_needed (true)
142 , _optimization_iterator (_events
.end())
144 , _no_sound_notes (false)
148 _note_group
->raise_to_top();
149 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
151 connect_to_diskstream ();
155 MidiRegionView::parameter_changed (std::string
const & p
)
157 if (p
== "diplay-first-midi-bank-as-zero") {
158 if (_enable_display
) {
164 MidiRegionView::MidiRegionView (const MidiRegionView
& other
)
165 : sigc::trackable(other
)
168 , _last_channel_selection(0xFFFF)
169 , _model_name(string())
170 , _custom_device_mode(string())
172 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
173 , _note_diff_command (0)
176 , _step_edit_cursor (0)
177 , _step_edit_cursor_width (1.0)
178 , _step_edit_cursor_position (0.0)
179 , _channel_selection_scoped_note (0)
180 , _temporary_note_group (0)
183 , _sort_needed (true)
184 , _optimization_iterator (_events
.end())
186 , _no_sound_notes (false)
193 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
194 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
199 MidiRegionView::MidiRegionView (const MidiRegionView
& other
, boost::shared_ptr
<MidiRegion
> region
)
200 : RegionView (other
, boost::shared_ptr
<Region
> (region
))
202 , _last_channel_selection(0xFFFF)
203 , _model_name(string())
204 , _custom_device_mode(string())
206 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
207 , _note_diff_command (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)
217 , _sort_needed (true)
218 , _optimization_iterator (_events
.end())
220 , _no_sound_notes (false)
227 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
228 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
234 MidiRegionView::init (Gdk::Color
const & basic_color
, bool wfd
)
236 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
238 CanvasNoteEvent::CanvasNoteEventDeleted
.connect (note_delete_connection
, MISSING_INVALIDATOR
,
239 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection
, this, _1
),
243 midi_region()->midi_source(0)->load_model();
246 _model
= midi_region()->midi_source(0)->model();
247 _enable_display
= false;
249 RegionView::init (basic_color
, false);
251 compute_colors (basic_color
);
253 set_height (trackview
.current_height());
256 region_sync_changed ();
257 region_resized (ARDOUR::bounds_change
);
260 reset_width_dependent_items (_pixel_width
);
264 _enable_display
= true;
267 display_model (_model
);
271 group
->raise_to_top();
272 group
->signal_event().connect(
273 sigc::mem_fun(this, &MidiRegionView::canvas_event
), false);
275 midi_view()->signal_channel_mode_changed().connect(
276 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed
));
278 midi_view()->signal_midi_patch_settings_changed().connect(
279 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed
));
281 trackview
.editor().SnapChanged
.connect(snap_changed_connection
, invalidator(*this),
282 ui_bind(&MidiRegionView::snap_changed
, this),
285 Config
->ParameterChanged
.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed
, this, _1
), gui_context());
286 connect_to_diskstream ();
290 MidiRegionView::connect_to_diskstream ()
292 midi_view()->midi_track()->DataRecorded
.connect(
293 *this, invalidator(*this),
294 ui_bind(&MidiRegionView::data_recorded
, this, _1
, _2
),
299 MidiRegionView::canvas_event(GdkEvent
* ev
)
302 case GDK_ENTER_NOTIFY
:
303 case GDK_LEAVE_NOTIFY
:
304 _last_event_x
= ev
->crossing
.x
;
305 _last_event_y
= ev
->crossing
.y
;
307 case GDK_MOTION_NOTIFY
:
308 _last_event_x
= ev
->motion
.x
;
309 _last_event_y
= ev
->motion
.y
;
315 if (!trackview
.editor().internal_editing()) {
321 return scroll (&ev
->scroll
);
324 return key_press (&ev
->key
);
326 case GDK_KEY_RELEASE
:
327 return key_release (&ev
->key
);
329 case GDK_BUTTON_PRESS
:
330 return button_press (&ev
->button
);
332 case GDK_2BUTTON_PRESS
:
335 case GDK_BUTTON_RELEASE
:
336 return button_release (&ev
->button
);
338 case GDK_ENTER_NOTIFY
:
339 return enter_notify (&ev
->crossing
);
341 case GDK_LEAVE_NOTIFY
:
342 return leave_notify (&ev
->crossing
);
344 case GDK_MOTION_NOTIFY
:
345 return motion (&ev
->motion
);
355 MidiRegionView::remove_ghost_note ()
362 MidiRegionView::enter_notify (GdkEventCrossing
* ev
)
364 trackview
.editor().MouseModeChanged
.connect (
365 _mouse_mode_connection
, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed
, this), gui_context ()
368 if (trackview
.editor().current_mouse_mode() == MouseRange
) {
369 create_ghost_note (ev
->x
, ev
->y
);
372 if (!trackview
.editor().internal_editing()) {
373 Keyboard::magic_widget_drop_focus();
375 Keyboard::magic_widget_grab_focus();
383 MidiRegionView::leave_notify (GdkEventCrossing
* ev
)
385 _mouse_mode_connection
.disconnect ();
387 trackview
.editor().verbose_cursor()->hide ();
388 remove_ghost_note ();
394 MidiRegionView::mouse_mode_changed ()
396 if (trackview
.editor().current_mouse_mode() == MouseRange
&& trackview
.editor().internal_editing()) {
397 create_ghost_note (_last_event_x
, _last_event_y
);
399 remove_ghost_note ();
400 trackview
.editor().verbose_cursor()->hide ();
403 if (!trackview
.editor().internal_editing()) {
404 Keyboard::magic_widget_drop_focus();
406 Keyboard::magic_widget_grab_focus();
412 MidiRegionView::button_press (GdkEventButton
* ev
)
414 if (ev
->button
!= 1) {
421 group
->w2i (_last_x
, _last_y
);
423 if (_mouse_state
!= SelectTouchDragging
) {
425 _pressed_button
= ev
->button
;
426 _mouse_state
= Pressed
;
431 _pressed_button
= ev
->button
;
437 MidiRegionView::button_release (GdkEventButton
* ev
)
439 double event_x
, event_y
;
441 if (ev
->button
!= 1) {
448 group
->w2i(event_x
, event_y
);
449 group
->ungrab(ev
->time
);
451 switch (_mouse_state
) {
452 case Pressed
: // Clicked
454 switch (trackview
.editor().current_mouse_mode()) {
460 if (Keyboard::is_insert_note_event(ev
)) {
462 double event_x
, event_y
;
466 group
->w2i(event_x
, event_y
);
469 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
475 create_note_at (event_x
, event_y
, beats
, true);
483 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
489 create_note_at (event_x
, event_y
, beats
, true);
500 case SelectRectDragging
: // Select drag done
507 case AddDragging
: // Add drag done
511 if (Keyboard::is_insert_note_event(ev
) || trackview
.editor().current_mouse_mode() == MouseRange
) {
513 if (_drag_rect
->property_x2() > _drag_rect
->property_x1() + 2) {
515 const double x
= _drag_rect
->property_x1();
516 const double length
= trackview
.editor().pixel_to_frame (_drag_rect
->property_x2() - _drag_rect
->property_x1());
518 create_note_at (x
, _drag_rect
->property_y1(), region_frames_to_region_beats(length
), true);
525 create_ghost_note (ev
->x
, ev
->y
);
535 MidiRegionView::motion (GdkEventMotion
* ev
)
537 double event_x
, event_y
;
538 framepos_t event_frame
= 0;
542 group
->w2i(event_x
, event_y
);
544 // convert event_x to global frame
545 event_frame
= snap_pixel_to_frame (event_x
);
547 if (!_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
548 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())
549 && _mouse_state
!= AddDragging
) {
551 create_ghost_note (ev
->x
, ev
->y
);
552 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
553 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())) {
555 update_ghost_note (ev
->x
, ev
->y
);
556 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
) {
561 trackview
.editor().verbose_cursor()->hide ();
562 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() == MouseRange
) {
563 update_ghost_note (ev
->x
, ev
->y
);
566 /* any motion immediately hides velocity text that may have been visible */
568 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
569 (*i
)->hide_velocity ();
572 switch (_mouse_state
) {
573 case Pressed
: // Maybe start a drag, if we've moved a bit
575 if (fabs (event_x
- _last_x
) < 1 && fabs (event_y
- _last_y
) < 1) {
576 /* no appreciable movement since the button was pressed */
580 if (_pressed_button
== 1 && trackview
.editor().current_mouse_mode() == MouseObject
581 && !Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())) {
584 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
585 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
589 _drag_start_x
= event_x
;
590 _drag_start_y
= event_y
;
592 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
593 _drag_rect
->property_x1() = event_x
;
594 _drag_rect
->property_y1() = event_y
;
595 _drag_rect
->property_x2() = event_x
;
596 _drag_rect
->property_y2() = event_y
;
597 _drag_rect
->property_outline_what() = 0xFF;
598 _drag_rect
->property_outline_color_rgba()
599 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
600 _drag_rect
->property_fill_color_rgba()
601 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
603 _mouse_state
= SelectRectDragging
;
606 } else if (trackview
.editor().internal_editing()) {
607 // Add note drag start
609 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
610 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
614 _drag_start_x
= event_x
;
615 _drag_start_y
= event_y
;
617 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
618 _drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
620 _drag_rect
->property_y1() = midi_stream_view()->note_to_y(
621 midi_stream_view()->y_to_note(event_y
));
622 _drag_rect
->property_x2() = trackview
.editor().frame_to_pixel(event_frame
);
623 _drag_rect
->property_y2() = _drag_rect
->property_y1()
624 + floor(midi_stream_view()->note_height());
625 _drag_rect
->property_outline_what() = 0xFF;
626 _drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
627 _drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
629 _mouse_state
= AddDragging
;
634 trackview
.editor().verbose_cursor()->hide ();
641 case SelectRectDragging
: // Select drag motion
642 case AddDragging
: // Add note drag motion
647 GdkModifierType state
;
648 gdk_window_get_pointer(ev
->window
, &t_x
, &t_y
, &state
);
653 if (_mouse_state
== AddDragging
) {
654 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
659 if (event_x
> _drag_start_x
) {
660 _drag_rect
->property_x2() = event_x
;
663 _drag_rect
->property_x1() = event_x
;
667 if (_drag_rect
&& _mouse_state
== SelectRectDragging
) {
669 if (event_y
> _drag_start_y
) {
670 _drag_rect
->property_y2() = event_y
;
673 _drag_rect
->property_y1() = event_y
;
676 update_drag_selection(_drag_start_x
, event_x
, _drag_start_y
, event_y
, Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
682 case SelectTouchDragging
:
694 MidiRegionView::scroll (GdkEventScroll
* ev
)
696 if (_selection
.empty()) {
700 trackview
.editor().verbose_cursor()->hide ();
702 bool fine
= !Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
);
704 if (ev
->direction
== GDK_SCROLL_UP
) {
705 change_velocities (true, fine
, false);
706 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
707 change_velocities (false, fine
, false);
713 MidiRegionView::key_press (GdkEventKey
* ev
)
715 /* since GTK bindings are generally activated on press, and since
716 detectable auto-repeat is the name of the game and only sends
717 repeated presses, carry out key actions at key press, not release.
720 bool unmodified
= Keyboard::no_modifier_keys_pressed (ev
);
722 if (unmodified
&& (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
)) {
723 _mouse_state
= SelectTouchDragging
;
726 } else if (ev
->keyval
== GDK_Escape
&& unmodified
) {
730 } else if (unmodified
&& (ev
->keyval
== GDK_comma
|| ev
->keyval
== GDK_period
)) {
732 bool start
= (ev
->keyval
== GDK_comma
);
733 bool end
= (ev
->keyval
== GDK_period
);
734 bool shorter
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
);
735 bool fine
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
737 change_note_lengths (fine
, shorter
, 0.0, start
, end
);
741 } else if (ev
->keyval
== GDK_Delete
&& unmodified
) {
746 } else if (ev
->keyval
== GDK_Tab
) {
748 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
749 goto_previous_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
751 goto_next_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
755 } else if (ev
->keyval
== GDK_ISO_Left_Tab
) {
757 /* Shift-TAB generates ISO Left Tab, for some reason */
759 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
760 goto_previous_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
762 goto_next_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
768 } else if (ev
->keyval
== GDK_Up
) {
770 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
771 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
773 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
774 change_velocities (true, fine
, allow_smush
);
776 transpose (true, fine
, allow_smush
);
780 } else if (ev
->keyval
== GDK_Down
) {
782 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
783 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
785 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
786 change_velocities (false, fine
, allow_smush
);
788 transpose (false, fine
, allow_smush
);
792 } else if (ev
->keyval
== GDK_Left
&& unmodified
) {
797 } else if (ev
->keyval
== GDK_Right
&& unmodified
) {
802 } else if (ev
->keyval
== GDK_c
&& unmodified
) {
811 MidiRegionView::key_release (GdkEventKey
* ev
)
813 if ((_mouse_state
== SelectTouchDragging
) && (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
)) {
821 MidiRegionView::channel_edit ()
824 uint8_t current_channel
;
826 if (_selection
.empty()) {
830 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
832 current_channel
= (*i
)->note()->channel ();
837 MidiChannelDialog
channel_dialog (current_channel
);
838 int ret
= channel_dialog
.run ();
841 case Gtk::RESPONSE_OK
:
847 uint8_t new_channel
= channel_dialog
.active_channel ();
849 start_note_diff_command (_("channel edit"));
851 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
852 Selection::iterator next
= i
;
854 change_note_channel (*i
, new_channel
);
862 MidiRegionView::show_list_editor ()
865 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
867 _list_editor
->present ();
870 /** Add a note to the model, and the view, at a canvas (click) coordinate.
871 * \param x horizontal position in pixels
872 * \param y vertical position in pixels
873 * \param length duration of the note in beats, which will be snapped to the grid
874 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
877 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
879 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
880 MidiStreamView
* const view
= mtv
->midi_view();
882 double note
= view
->y_to_note(y
);
885 assert(note
<= 127.0);
887 // Start of note in frames relative to region start
888 framepos_t
const start_frames
= snap_pixel_to_frame (x
);
889 assert(start_frames
>= 0);
892 length
= region_frames_to_region_beats(
893 snap_frame_to_frame (start_frames
+ region_beats_to_region_frames(length
)) - start_frames
);
895 assert (length
!= 0);
898 length
= region_frames_to_region_beats (region_beats_to_region_frames (length
) - 1);
901 const boost::shared_ptr
<NoteType
> new_note (new NoteType (mtv
->get_channel_for_add (),
902 region_frames_to_region_beats(start_frames
+ _region
->start()), length
,
903 (uint8_t)note
, 0x40));
905 if (_model
->contains (new_note
)) {
909 view
->update_note_range(new_note
->note());
911 MidiModel::NoteDiffCommand
* cmd
= _model
->new_note_diff_command("add note");
913 _model
->apply_command(*trackview
.session(), cmd
);
915 play_midi_note (new_note
);
919 MidiRegionView::clear_events()
924 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
925 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
930 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
935 _patch_changes
.clear();
937 _optimization_iterator
= _events
.end();
941 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
945 content_connection
.disconnect ();
946 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
950 if (_enable_display
) {
956 MidiRegionView::start_note_diff_command (string name
)
958 if (!_note_diff_command
) {
959 _note_diff_command
= _model
->new_note_diff_command (name
);
964 MidiRegionView::note_diff_add_note (const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
966 if (_note_diff_command
) {
967 _note_diff_command
->add (note
);
970 _marked_for_selection
.insert(note
);
973 _marked_for_velocity
.insert(note
);
978 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent
* ev
)
980 if (_note_diff_command
&& ev
->note()) {
981 _note_diff_command
->remove(ev
->note());
986 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
987 MidiModel::NoteDiffCommand::Property property
,
990 if (_note_diff_command
) {
991 _note_diff_command
->change (ev
->note(), property
, val
);
996 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
997 MidiModel::NoteDiffCommand::Property property
,
998 Evoral::MusicalTime val
)
1000 if (_note_diff_command
) {
1001 _note_diff_command
->change (ev
->note(), property
, val
);
1006 MidiRegionView::apply_diff (bool as_subcommand
)
1010 if (!_note_diff_command
) {
1014 if ((add_or_remove
= _note_diff_command
->adds_or_removes())) {
1015 // Mark all selected notes for selection when model reloads
1016 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1017 _marked_for_selection
.insert((*i
)->note());
1021 if (as_subcommand
) {
1022 _model
->apply_command_as_subcommand (*trackview
.session(), _note_diff_command
);
1024 _model
->apply_command (*trackview
.session(), _note_diff_command
);
1027 _note_diff_command
= 0;
1028 midi_view()->midi_track()->playlist_modified();
1030 if (add_or_remove
) {
1031 _marked_for_selection
.clear();
1034 _marked_for_velocity
.clear();
1038 MidiRegionView::abort_command()
1040 delete _note_diff_command
;
1041 _note_diff_command
= 0;
1046 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
1048 if (_optimization_iterator
!= _events
.end()) {
1049 ++_optimization_iterator
;
1052 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
1053 return *_optimization_iterator
;
1056 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
1057 if ((*_optimization_iterator
)->note() == note
) {
1058 return *_optimization_iterator
;
1066 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
1068 MidiModel::Notes notes
;
1069 _model
->get_notes (notes
, op
, val
, chan_mask
);
1071 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1072 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
1080 MidiRegionView::redisplay_model()
1082 // Don't redisplay the model if we're currently recording and displaying that
1083 if (_active_notes
) {
1091 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1092 (*i
)->invalidate ();
1095 MidiModel::ReadLock
lock(_model
->read_lock());
1097 MidiModel::Notes
& notes (_model
->notes());
1098 _optimization_iterator
= _events
.begin();
1100 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1102 boost::shared_ptr
<NoteType
> note (*n
);
1103 CanvasNoteEvent
* cne
;
1106 if (note_in_region_range (note
, visible
)) {
1108 if ((cne
= find_canvas_note (note
)) != 0) {
1115 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
1117 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
1129 add_note (note
, visible
);
1134 if ((cne
= find_canvas_note (note
)) != 0) {
1142 /* remove note items that are no longer valid */
1144 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
1145 if (!(*i
)->valid ()) {
1147 i
= _events
.erase (i
);
1153 _patch_changes
.clear();
1157 display_patch_changes ();
1159 _marked_for_selection
.clear ();
1160 _marked_for_velocity
.clear ();
1162 /* we may have caused _events to contain things out of order (e.g. if a note
1163 moved earlier or later). we don't generally need them in time order, but
1164 make a note that a sort is required for those cases that require it.
1167 _sort_needed
= true;
1171 MidiRegionView::display_patch_changes ()
1173 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1174 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
1176 for (uint8_t i
= 0; i
< 16; ++i
) {
1177 if (chn_mask
& (1<<i
)) {
1178 display_patch_changes_on_channel (i
);
1180 /* TODO gray-out patch instad of not displaying it */
1185 MidiRegionView::display_patch_changes_on_channel (uint8_t channel
)
1187 for (MidiModel::PatchChanges::const_iterator i
= _model
->patch_changes().begin(); i
!= _model
->patch_changes().end(); ++i
) {
1189 if ((*i
)->channel() != channel
) {
1193 MIDI::Name::PatchPrimaryKey
patch_key ((*i
)->bank_msb (), (*i
)->bank_lsb (), (*i
)->program ());
1195 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1196 MIDI::Name::MidiPatchManager::instance().find_patch(
1197 _model_name
, _custom_device_mode
, channel
, patch_key
);
1200 add_canvas_patch_change (*i
, patch
->name());
1203 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1204 snprintf (buf
, 16, "%d %d", (*i
)->program() + MIDI_BP_ZERO
, (*i
)->bank() + MIDI_BP_ZERO
);
1205 add_canvas_patch_change (*i
, buf
);
1211 MidiRegionView::display_sysexes()
1213 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1214 Evoral::MusicalTime time
= (*i
)->time();
1219 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1220 str
<< int((*i
)->buffer()[b
]);
1221 if (b
!= (*i
)->size() -1) {
1225 string text
= str
.str();
1227 const double x
= trackview
.editor().frame_to_pixel(source_beats_to_absolute_frames(time
));
1229 double height
= midi_stream_view()->contents_height();
1231 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1232 new CanvasSysEx(*this, *_note_group
, text
, height
, x
, 1.0));
1234 // Show unless patch change is beyond the region bounds
1235 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1241 _sys_exes
.push_back(sysex
);
1246 MidiRegionView::~MidiRegionView ()
1248 in_destructor
= true;
1250 trackview
.editor().verbose_cursor()->hide ();
1252 note_delete_connection
.disconnect ();
1254 delete _list_editor
;
1256 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1258 if (_active_notes
) {
1266 delete _note_diff_command
;
1267 delete _step_edit_cursor
;
1268 delete _temporary_note_group
;
1272 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1274 RegionView::region_resized(what_changed
);
1276 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1277 set_duration(_region
->length(), 0);
1278 if (_enable_display
) {
1285 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1287 RegionView::reset_width_dependent_items(pixel_width
);
1288 assert(_pixel_width
== pixel_width
);
1290 if (_enable_display
) {
1294 move_step_edit_cursor (_step_edit_cursor_position
);
1295 set_step_edit_cursor_width (_step_edit_cursor_width
);
1299 MidiRegionView::set_height (double height
)
1301 static const double FUDGE
= 2.0;
1302 const double old_height
= _height
;
1303 RegionView::set_height(height
);
1304 _height
= height
- FUDGE
;
1306 apply_note_range(midi_stream_view()->lowest_note(),
1307 midi_stream_view()->highest_note(),
1308 height
!= old_height
+ FUDGE
);
1311 name_pixbuf
->raise_to_top();
1314 for (PatchChanges::iterator x
= _patch_changes
.begin(); x
!= _patch_changes
.end(); ++x
) {
1315 (*x
)->set_height (midi_stream_view()->contents_height());
1318 if (_step_edit_cursor
) {
1319 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
1324 /** Apply the current note range from the stream view
1325 * by repositioning/hiding notes as necessary
1328 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1330 if (!_enable_display
) {
1334 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1338 _current_range_min
= min
;
1339 _current_range_max
= max
;
1341 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1342 CanvasNoteEvent
* event
= *i
;
1343 boost::shared_ptr
<NoteType
> note (event
->note());
1345 if (note
->note() < _current_range_min
||
1346 note
->note() > _current_range_max
) {
1352 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1354 const double y1
= midi_stream_view()->note_to_y(note
->note());
1355 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1357 cnote
->property_y1() = y1
;
1358 cnote
->property_y2() = y2
;
1360 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1362 const double diamond_size
= update_hit (chit
);
1364 chit
->set_height (diamond_size
);
1370 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1374 double unit_position
= _region
->position () / samples_per_unit
;
1375 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1376 MidiGhostRegion
* ghost
;
1378 if (mtv
&& mtv
->midi_view()) {
1379 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1380 to allow having midi notes on top of note lines and waveforms.
1382 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1384 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1387 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1388 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1389 ghost
->add_note(note
);
1393 ghost
->set_height ();
1394 ghost
->set_duration (_region
->length() / samples_per_unit
);
1395 ghosts
.push_back (ghost
);
1397 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1403 /** Begin tracking note state for successive calls to add_event
1406 MidiRegionView::begin_write()
1408 assert(!_active_notes
);
1409 _active_notes
= new CanvasNote
*[128];
1410 for (unsigned i
=0; i
< 128; ++i
) {
1411 _active_notes
[i
] = 0;
1416 /** Destroy note state for add_event
1419 MidiRegionView::end_write()
1421 delete[] _active_notes
;
1423 _marked_for_selection
.clear();
1424 _marked_for_velocity
.clear();
1428 /** Resolve an active MIDI note (while recording).
1431 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1433 if (midi_view()->note_mode() != Sustained
) {
1437 if (_active_notes
&& _active_notes
[note
]) {
1439 /* XXX is end_time really region-centric? I think so, because
1440 this is a new region that we're recording, so source zero is
1441 the same as region zero
1443 const framepos_t end_time_frames
= region_beats_to_region_frames(end_time
);
1445 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1446 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1447 _active_notes
[note
] = 0;
1452 /** Extend active notes to rightmost edge of region (if length is changed)
1455 MidiRegionView::extend_active_notes()
1457 if (!_active_notes
) {
1461 for (unsigned i
=0; i
< 128; ++i
) {
1462 if (_active_notes
[i
]) {
1463 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1470 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1472 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1476 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1478 if (!route_ui
|| !route_ui
->midi_track()) {
1482 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1488 MidiRegionView::play_midi_chord (vector
<boost::shared_ptr
<NoteType
> > notes
)
1490 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1494 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1496 if (!route_ui
|| !route_ui
->midi_track()) {
1500 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1502 for (vector
<boost::shared_ptr
<NoteType
> >::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1511 MidiRegionView::note_in_region_range (const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1513 const framepos_t note_start_frames
= source_beats_to_region_frames (note
->time());
1514 bool outside
= (note_start_frames
< 0) || (note_start_frames
> _region
->last_frame());
1516 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1517 (note
->note() <= midi_stream_view()->highest_note());
1522 /** Update a canvas note's size from its model note.
1523 * @param ev Canvas note to update.
1524 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1527 MidiRegionView::update_note (CanvasNote
* ev
, bool update_ghost_regions
)
1529 boost::shared_ptr
<NoteType
> note
= ev
->note();
1531 const double x
= trackview
.editor().frame_to_pixel (source_beats_to_region_frames (note
->time()));
1532 const double y1
= midi_stream_view()->note_to_y(note
->note());
1534 ev
->property_x1() = x
;
1535 ev
->property_y1() = y1
;
1537 /* trim note display to not overlap the end of its region */
1539 if (note
->length() > 0) {
1540 const framepos_t note_end_frames
= min (source_beats_to_region_frames (note
->end_time()), _region
->length());
1541 ev
->property_x2() = trackview
.editor().frame_to_pixel (note_end_frames
);
1543 ev
->property_x2() = trackview
.editor().frame_to_pixel (_region
->length());
1546 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1548 if (note
->length() == 0) {
1549 if (_active_notes
) {
1550 assert(note
->note() < 128);
1551 // If this note is already active there's a stuck note,
1552 // finish the old note rectangle
1553 if (_active_notes
[note
->note()]) {
1554 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1555 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1556 old_rect
->property_x2() = x
;
1557 old_rect
->property_outline_what() = (guint32
) 0xF;
1559 _active_notes
[note
->note()] = ev
;
1561 /* outline all but right edge */
1562 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1564 /* outline all edges */
1565 ev
->property_outline_what() = (guint32
) 0xF;
1568 if (update_ghost_regions
) {
1569 for (std::vector
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
1570 MidiGhostRegion
* gr
= dynamic_cast<MidiGhostRegion
*> (*i
);
1572 gr
->update_note (ev
);
1579 MidiRegionView::update_hit (CanvasHit
* ev
)
1581 boost::shared_ptr
<NoteType
> note
= ev
->note();
1583 const framepos_t note_start_frames
= source_beats_to_region_frames(note
->time());
1584 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
);
1585 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1586 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1590 return diamond_size
;
1593 /** Add a MIDI note to the view (with length).
1595 * If in sustained mode, notes with length 0 will be considered active
1596 * notes, and resolve_note should be called when the corresponding note off
1597 * event arrives, to properly display the note.
1600 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1602 CanvasNoteEvent
* event
= 0;
1604 assert(note
->time() >= 0);
1605 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1607 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1609 if (midi_view()->note_mode() == Sustained
) {
1611 CanvasNote
* ev_rect
= new CanvasNote(*this, *_note_group
, note
);
1613 update_note (ev_rect
);
1617 MidiGhostRegion
* gr
;
1619 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1620 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1621 gr
->add_note(ev_rect
);
1625 } else if (midi_view()->note_mode() == Percussive
) {
1627 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1629 CanvasHit
* ev_diamond
= new CanvasHit (*this, *_note_group
, diamond_size
, note
);
1631 update_hit (ev_diamond
);
1640 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1641 note_selected(event
, true);
1644 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1645 event
->show_velocity();
1648 event
->on_channel_selection_change(_last_channel_selection
);
1649 _events
.push_back(event
);
1658 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1659 MidiStreamView
* const view
= mtv
->midi_view();
1661 view
->update_note_range (note
->note());
1665 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1666 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1668 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1670 /* potentially extend region to hold new note */
1672 framepos_t end_frame
= source_beats_to_absolute_frames (new_note
->end_time());
1673 framepos_t region_end
= _region
->last_frame();
1675 if (end_frame
> region_end
) {
1676 _region
->set_length (end_frame
- _region
->position());
1679 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1680 MidiStreamView
* const view
= mtv
->midi_view();
1682 view
->update_note_range(new_note
->note());
1684 _marked_for_selection
.clear ();
1687 start_note_diff_command (_("step add"));
1688 note_diff_add_note (new_note
, true, false);
1691 // last_step_edit_note = new_note;
1695 MidiRegionView::step_sustain (Evoral::MusicalTime beats
)
1697 change_note_lengths (false, false, beats
, false, true);
1701 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch
, const string
& displaytext
)
1703 assert (patch
->time() >= 0);
1705 const double x
= trackview
.editor().frame_to_pixel (source_beats_to_region_frames (patch
->time()));
1707 double const height
= midi_stream_view()->contents_height();
1709 boost::shared_ptr
<CanvasPatchChange
> patch_change
= boost::shared_ptr
<CanvasPatchChange
>(
1710 new CanvasPatchChange(*this, *_note_group
,
1715 _custom_device_mode
,
1719 // Show unless patch change is beyond the region bounds
1720 if (patch
->time() - _region
->start() >= _region
->length() || patch
->time() < _region
->start()) {
1721 patch_change
->hide();
1723 patch_change
->show();
1726 _patch_changes
.push_back (patch_change
);
1730 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1732 MidiModel::PatchChanges::iterator i
= _model
->patch_change_lower_bound (time
);
1733 while (i
!= _model
->patch_changes().end() && (*i
)->channel() != channel
) {
1737 if (i
!= _model
->patch_changes().end()) {
1738 key
.msb
= (*i
)->bank_msb ();
1739 key
.lsb
= (*i
)->bank_lsb ();
1740 key
.program_number
= (*i
)->program ();
1742 key
.msb
= key
.lsb
= key
.program_number
= 0;
1745 assert (key
.is_sane());
1750 MidiRegionView::change_patch_change (CanvasPatchChange
& pc
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1752 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1754 if (pc
.patch()->program() != new_patch
.program_number
) {
1755 c
->change_program (pc
.patch (), new_patch
.program_number
);
1758 int const new_bank
= (new_patch
.msb
<< 7) | new_patch
.lsb
;
1759 if (pc
.patch()->bank() != new_bank
) {
1760 c
->change_bank (pc
.patch (), new_bank
);
1763 _model
->apply_command (*trackview
.session(), c
);
1765 _patch_changes
.clear ();
1766 display_patch_changes ();
1770 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change
, const Evoral::PatchChange
<Evoral::MusicalTime
> & new_change
)
1772 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1774 if (old_change
->time() != new_change
.time()) {
1775 c
->change_time (old_change
, new_change
.time());
1778 if (old_change
->channel() != new_change
.channel()) {
1779 c
->change_channel (old_change
, new_change
.channel());
1782 if (old_change
->program() != new_change
.program()) {
1783 c
->change_program (old_change
, new_change
.program());
1786 if (old_change
->bank() != new_change
.bank()) {
1787 c
->change_bank (old_change
, new_change
.bank());
1790 _model
->apply_command (*trackview
.session(), c
);
1792 _patch_changes
.clear ();
1793 display_patch_changes ();
1796 /** Add a patch change to the region.
1797 * @param t Time in frames relative to region position
1798 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1799 * MidiTimeAxisView::get_channel_for_add())
1802 MidiRegionView::add_patch_change (framecnt_t t
, Evoral::PatchChange
<Evoral::MusicalTime
> const & patch
)
1804 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1806 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("add patch change"));
1807 c
->add (MidiModel::PatchChangePtr (
1808 new Evoral::PatchChange
<Evoral::MusicalTime
> (
1809 absolute_frames_to_source_beats (_region
->position() + t
),
1810 mtv
->get_channel_for_add(), patch
.program(), patch
.bank()
1815 _model
->apply_command (*trackview
.session(), c
);
1817 _patch_changes
.clear ();
1818 display_patch_changes ();
1822 MidiRegionView::move_patch_change (CanvasPatchChange
& pc
, Evoral::MusicalTime t
)
1824 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("move patch change"));
1825 c
->change_time (pc
.patch (), t
);
1826 _model
->apply_command (*trackview
.session(), c
);
1828 _patch_changes
.clear ();
1829 display_patch_changes ();
1833 MidiRegionView::delete_patch_change (CanvasPatchChange
* pc
)
1835 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("delete patch change"));
1836 c
->remove (pc
->patch ());
1837 _model
->apply_command (*trackview
.session(), c
);
1839 _patch_changes
.clear ();
1840 display_patch_changes ();
1844 MidiRegionView::previous_patch (CanvasPatchChange
& patch
)
1846 if (patch
.patch()->program() < 127) {
1847 MIDI::Name::PatchPrimaryKey key
;
1848 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1849 key
.program_number
++;
1850 change_patch_change (patch
, key
);
1855 MidiRegionView::next_patch (CanvasPatchChange
& patch
)
1857 if (patch
.patch()->program() > 0) {
1858 MIDI::Name::PatchPrimaryKey key
;
1859 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1860 key
.program_number
--;
1861 change_patch_change (patch
, key
);
1866 MidiRegionView::previous_bank (CanvasPatchChange
& patch
)
1868 if (patch
.patch()->program() < 127) {
1869 MIDI::Name::PatchPrimaryKey key
;
1870 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1873 change_patch_change (patch
, key
);
1878 change_patch_change (patch
, key
);
1885 MidiRegionView::next_bank (CanvasPatchChange
& patch
)
1887 if (patch
.patch()->program() > 0) {
1888 MIDI::Name::PatchPrimaryKey key
;
1889 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1890 if (key
.lsb
< 127) {
1892 change_patch_change (patch
, key
);
1894 if (key
.msb
< 127) {
1897 change_patch_change (patch
, key
);
1904 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1906 if (_selection
.empty()) {
1910 _selection
.erase (cne
);
1914 MidiRegionView::delete_selection()
1916 if (_selection
.empty()) {
1920 start_note_diff_command (_("delete selection"));
1922 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1923 if ((*i
)->selected()) {
1924 _note_diff_command
->remove((*i
)->note());
1934 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1936 start_note_diff_command (_("delete note"));
1937 _note_diff_command
->remove (n
);
1940 trackview
.editor().verbose_cursor()->hide ();
1944 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent
* ev
)
1946 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1948 Selection::iterator tmp
= i
;
1951 (*i
)->set_selected (false);
1952 (*i
)->hide_velocity ();
1953 _selection
.erase (i
);
1961 /* this does not change the status of this regionview w.r.t the editor
1967 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1969 clear_selection_except (ev
);
1971 /* don't bother with checking to see if we should remove this
1972 regionview from the editor selection, since we're about to add
1973 another note, and thus put/keep this regionview in the editor
1977 if (!ev
->selected()) {
1978 add_to_selection (ev
);
1983 MidiRegionView::select_all_notes ()
1987 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1988 add_to_selection (*i
);
1993 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1995 uint8_t low_note
= 127;
1996 uint8_t high_note
= 0;
1997 MidiModel::Notes
& notes (_model
->notes());
1998 _optimization_iterator
= _events
.begin();
2004 if (extend
&& _selection
.empty()) {
2010 /* scan existing selection to get note range */
2012 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2013 if ((*i
)->note()->note() < low_note
) {
2014 low_note
= (*i
)->note()->note();
2016 if ((*i
)->note()->note() > high_note
) {
2017 high_note
= (*i
)->note()->note();
2021 low_note
= min (low_note
, notenum
);
2022 high_note
= max (high_note
, notenum
);
2025 _no_sound_notes
= true;
2027 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
2029 boost::shared_ptr
<NoteType
> note (*n
);
2030 CanvasNoteEvent
* cne
;
2031 bool select
= false;
2033 if (((1 << note
->channel()) & channel_mask
) != 0) {
2035 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
2038 } else if (note
->note() == notenum
) {
2044 if ((cne
= find_canvas_note (note
)) != 0) {
2045 // extend is false because we've taken care of it,
2046 // since it extends by time range, not pitch.
2047 note_selected (cne
, add
, false);
2051 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2055 _no_sound_notes
= false;
2059 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
2061 MidiModel::Notes
& notes (_model
->notes());
2062 _optimization_iterator
= _events
.begin();
2064 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
2066 boost::shared_ptr
<NoteType
> note (*n
);
2067 CanvasNoteEvent
* cne
;
2069 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
2070 if ((cne
= find_canvas_note (note
)) != 0) {
2071 if (cne
->selected()) {
2072 note_deselected (cne
);
2074 note_selected (cne
, true, false);
2082 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
2085 clear_selection_except (ev
);
2086 if (!_selection
.empty()) {
2087 PublicEditor
& editor (trackview
.editor());
2088 editor
.get_selection().add (this);
2094 if (!ev
->selected()) {
2095 add_to_selection (ev
);
2099 /* find end of latest note selected, select all between that and the start of "ev" */
2101 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2102 Evoral::MusicalTime latest
= 0;
2104 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2105 if ((*i
)->note()->end_time() > latest
) {
2106 latest
= (*i
)->note()->end_time();
2108 if ((*i
)->note()->time() < earliest
) {
2109 earliest
= (*i
)->note()->time();
2113 if (ev
->note()->end_time() > latest
) {
2114 latest
= ev
->note()->end_time();
2117 if (ev
->note()->time() < earliest
) {
2118 earliest
= ev
->note()->time();
2121 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2123 /* find notes entirely within OR spanning the earliest..latest range */
2125 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
2126 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
2127 add_to_selection (*i
);
2135 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
2137 remove_from_selection (ev
);
2141 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
, bool extend
)
2151 // TODO: Make this faster by storing the last updated selection rect, and only
2152 // adjusting things that are in the area that appears/disappeared.
2153 // We probably need a tree to be able to find events in O(log(n)) time.
2155 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2157 /* check if any corner of the note is inside the rect
2160 1) this is computing "touched by", not "contained by" the rect.
2161 2) this does not require that events be sorted in time.
2164 const double ix1
= (*i
)->x1();
2165 const double ix2
= (*i
)->x2();
2166 const double iy1
= (*i
)->y1();
2167 const double iy2
= (*i
)->y2();
2169 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2170 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
2171 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2172 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
2175 if (!(*i
)->selected()) {
2176 add_to_selection (*i
);
2178 } else if ((*i
)->selected() && !extend
) {
2179 // Not inside rectangle
2180 remove_from_selection (*i
);
2186 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
2188 Selection::iterator i
= _selection
.find (ev
);
2190 if (i
!= _selection
.end()) {
2191 _selection
.erase (i
);
2194 ev
->set_selected (false);
2195 ev
->hide_velocity ();
2197 if (_selection
.empty()) {
2198 PublicEditor
& editor (trackview
.editor());
2199 editor
.get_selection().remove (this);
2204 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
2206 bool add_mrv_selection
= false;
2208 if (_selection
.empty()) {
2209 add_mrv_selection
= true;
2212 if (_selection
.insert (ev
).second
) {
2213 ev
->set_selected (true);
2214 play_midi_note ((ev
)->note());
2217 if (add_mrv_selection
) {
2218 PublicEditor
& editor (trackview
.editor());
2219 editor
.get_selection().add (this);
2224 MidiRegionView::move_selection(double dx
, double dy
, double cumulative_dy
)
2226 typedef vector
<boost::shared_ptr
<NoteType
> > PossibleChord
;
2227 PossibleChord to_play
;
2228 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2230 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2231 if ((*i
)->note()->time() < earliest
) {
2232 earliest
= (*i
)->note()->time();
2236 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2237 if (Evoral::musical_time_equal ((*i
)->note()->time(), earliest
)) {
2238 to_play
.push_back ((*i
)->note());
2240 (*i
)->move_event(dx
, dy
);
2243 if (dy
&& !_selection
.empty() && !_no_sound_notes
&& trackview
.editor().sound_notes()) {
2245 if (to_play
.size() > 1) {
2247 PossibleChord shifted
;
2249 for (PossibleChord::iterator n
= to_play
.begin(); n
!= to_play
.end(); ++n
) {
2250 boost::shared_ptr
<NoteType
> moved_note (new NoteType (**n
));
2251 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2252 shifted
.push_back (moved_note
);
2255 play_midi_chord (shifted
);
2257 } else if (!to_play
.empty()) {
2259 boost::shared_ptr
<NoteType
> moved_note (new NoteType (*to_play
.front()));
2260 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2261 play_midi_note (moved_note
);
2267 MidiRegionView::note_dropped(CanvasNoteEvent
*, frameoffset_t dt
, int8_t dnote
)
2269 assert (!_selection
.empty());
2271 uint8_t lowest_note_in_selection
= 127;
2272 uint8_t highest_note_in_selection
= 0;
2273 uint8_t highest_note_difference
= 0;
2275 // find highest and lowest notes first
2277 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2278 uint8_t pitch
= (*i
)->note()->note();
2279 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
2280 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
2284 cerr << "dnote: " << (int) dnote << endl;
2285 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2286 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2287 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2288 << int(highest_note_in_selection) << endl;
2289 cerr << "selection size: " << _selection.size() << endl;
2290 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2293 // Make sure the note pitch does not exceed the MIDI standard range
2294 if (highest_note_in_selection
+ dnote
> 127) {
2295 highest_note_difference
= highest_note_in_selection
- 127;
2298 start_note_diff_command (_("move notes"));
2300 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
2302 Evoral::MusicalTime new_time
= absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i
)->note()->time()) + dt
);
2308 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2310 uint8_t original_pitch
= (*i
)->note()->note();
2311 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
2313 // keep notes in standard midi range
2314 clamp_to_0_127(new_pitch
);
2316 // keep original pitch if note is dragged outside valid midi range
2317 if ((original_pitch
!= 0 && new_pitch
== 0)
2318 || (original_pitch
!= 127 && new_pitch
== 127)) {
2319 new_pitch
= original_pitch
;
2322 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2323 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2325 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::NoteNumber
, new_pitch
);
2330 // care about notes being moved beyond the upper/lower bounds on the canvas
2331 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2332 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2333 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2338 MidiRegionView::snap_pixel_to_frame(double x
)
2340 PublicEditor
& editor (trackview
.editor());
2341 return snap_frame_to_frame (editor
.pixel_to_frame (x
));
2344 /** Snap a frame offset within our region using the current snap settings.
2345 * @param x Frame offset from this region's position.
2346 * @return Snapped frame offset from this region's position.
2349 MidiRegionView::snap_frame_to_frame (frameoffset_t x
)
2351 PublicEditor
& editor
= trackview
.editor();
2353 /* x is region relative, convert it to global absolute frames */
2354 framepos_t
const session_frame
= x
+ _region
->position();
2356 /* try a snap in either direction */
2357 framepos_t frame
= session_frame
;
2358 editor
.snap_to (frame
, 0);
2360 /* if we went off the beginning of the region, snap forwards */
2361 if (frame
< _region
->position ()) {
2362 frame
= session_frame
;
2363 editor
.snap_to (frame
, 1);
2366 /* back to region relative */
2367 return frame
- _region
->position();
2371 MidiRegionView::snap_to_pixel(double x
)
2373 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2377 MidiRegionView::get_position_pixels()
2379 framepos_t region_frame
= get_position();
2380 return trackview
.editor().frame_to_pixel(region_frame
);
2384 MidiRegionView::get_end_position_pixels()
2386 framepos_t frame
= get_position() + get_duration ();
2387 return trackview
.editor().frame_to_pixel(frame
);
2391 MidiRegionView::source_beats_to_absolute_frames(double beats
) const
2393 /* the time converter will return the frame corresponding to `beats'
2394 relative to the start of the source. The start of the source
2395 is an implied position given by region->position - region->start
2397 const framepos_t source_start
= _region
->position() - _region
->start();
2398 return source_start
+ _source_relative_time_converter
.to (beats
);
2402 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames
) const
2404 /* the `frames' argument needs to be converted into a frame count
2405 relative to the start of the source before being passed in to the
2408 const framepos_t source_start
= _region
->position() - _region
->start();
2409 return _source_relative_time_converter
.from (frames
- source_start
);
2413 MidiRegionView::region_beats_to_region_frames(double beats
) const
2415 return _region_relative_time_converter
.to(beats
);
2419 MidiRegionView::region_frames_to_region_beats(framepos_t frames
) const
2421 return _region_relative_time_converter
.from(frames
);
2425 MidiRegionView::begin_resizing (bool /*at_front*/)
2427 _resize_data
.clear();
2429 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2430 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2432 // only insert CanvasNotes into the map
2434 NoteResizeData
*resize_data
= new NoteResizeData();
2435 resize_data
->canvas_note
= note
;
2437 // create a new SimpleRect from the note which will be the resize preview
2438 SimpleRect
*resize_rect
= new SimpleRect(
2439 *_note_group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2441 // calculate the colors: get the color settings
2442 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2443 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2446 // make the resize preview notes more transparent and bright
2447 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2449 // calculate color based on note velocity
2450 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2451 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2455 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2456 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2458 resize_data
->resize_rect
= resize_rect
;
2459 _resize_data
.push_back(resize_data
);
2464 /** Update resizing notes while user drags.
2465 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2466 * @param at_front which end of the note (true == note on, false == note off)
2467 * @param delta_x change in mouse position since the start of the drag
2468 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2469 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2470 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2471 * as the \a primary note.
2474 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2476 bool cursor_set
= false;
2478 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2479 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2480 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2485 current_x
= canvas_note
->x1() + delta_x
;
2487 current_x
= primary
->x1() + delta_x
;
2491 current_x
= canvas_note
->x2() + delta_x
;
2493 current_x
= primary
->x2() + delta_x
;
2498 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2499 resize_rect
->property_x2() = canvas_note
->x2();
2501 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2502 resize_rect
->property_x1() = canvas_note
->x1();
2508 beats
= snap_pixel_to_frame (current_x
);
2509 /* XXX not sure this is correct - snap_pixel_to_frame()
2510 returns an absolute frame.
2512 beats
= region_frames_to_region_beats (beats
);
2517 if (beats
< canvas_note
->note()->end_time()) {
2518 len
= canvas_note
->note()->time() - beats
;
2519 len
+= canvas_note
->note()->length();
2524 if (beats
>= canvas_note
->note()->time()) {
2525 len
= beats
- canvas_note
->note()->time();
2532 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2533 show_verbose_cursor (buf
, 0, 0);
2542 /** Finish resizing notes when the user releases the mouse button.
2543 * Parameters the same as for \a update_resizing().
2546 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2548 start_note_diff_command (_("resize notes"));
2550 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2551 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2552 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2554 /* Get the new x position for this resize, which is in pixels relative
2555 * to the region position.
2562 current_x
= canvas_note
->x1() + delta_x
;
2564 current_x
= primary
->x1() + delta_x
;
2568 current_x
= canvas_note
->x2() + delta_x
;
2570 current_x
= primary
->x2() + delta_x
;
2574 /* Convert that to a frame within the region */
2575 current_x
= snap_pixel_to_frame (current_x
) + _region
->start ();
2577 /* and then to beats */
2578 /* XXX not sure this is correct - snap_pixel_to_frame()
2579 returns an absolute frame.
2581 current_x
= region_frames_to_region_beats (current_x
);
2583 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2584 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::StartTime
, current_x
);
2586 double len
= canvas_note
->note()->time() - current_x
;
2587 len
+= canvas_note
->note()->length();
2590 /* XXX convert to beats */
2591 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2596 double len
= current_x
- canvas_note
->note()->time();
2599 /* XXX convert to beats */
2600 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2608 _resize_data
.clear();
2613 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2615 uint8_t new_velocity
;
2618 new_velocity
= event
->note()->velocity() + velocity
;
2619 clamp_to_0_127(new_velocity
);
2621 new_velocity
= velocity
;
2624 event
->set_selected (event
->selected()); // change color
2626 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Velocity
, new_velocity
);
2630 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2635 new_note
= event
->note()->note() + note
;
2640 clamp_to_0_127 (new_note
);
2641 note_diff_add_change (event
, MidiModel::NoteDiffCommand::NoteNumber
, new_note
);
2645 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2647 bool change_start
= false;
2648 bool change_length
= false;
2649 Evoral::MusicalTime new_start
= 0;
2650 Evoral::MusicalTime new_length
= 0;
2652 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2654 front_delta: if positive - move the start of the note later in time (shortening it)
2655 if negative - move the start of the note earlier in time (lengthening it)
2657 end_delta: if positive - move the end of the note later in time (lengthening it)
2658 if negative - move the end of the note earlier in time (shortening it)
2662 if (front_delta
< 0) {
2664 if (event
->note()->time() < -front_delta
) {
2667 new_start
= event
->note()->time() + front_delta
; // moves earlier
2670 /* start moved toward zero, so move the end point out to where it used to be.
2671 Note that front_delta is negative, so this increases the length.
2674 new_length
= event
->note()->length() - front_delta
;
2675 change_start
= true;
2676 change_length
= true;
2680 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2682 if (new_pos
< event
->note()->end_time()) {
2683 new_start
= event
->note()->time() + front_delta
;
2684 /* start moved toward the end, so move the end point back to where it used to be */
2685 new_length
= event
->note()->length() - front_delta
;
2686 change_start
= true;
2687 change_length
= true;
2694 bool can_change
= true;
2695 if (end_delta
< 0) {
2696 if (event
->note()->length() < -end_delta
) {
2702 new_length
= event
->note()->length() + end_delta
;
2703 change_length
= true;
2708 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_start
);
2711 if (change_length
) {
2712 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, new_length
);
2717 MidiRegionView::change_note_channel (CanvasNoteEvent
* event
, int8_t chn
, bool relative
)
2719 uint8_t new_channel
;
2723 if (event
->note()->channel() < -chn
) {
2726 new_channel
= event
->note()->channel() + chn
;
2729 new_channel
= event
->note()->channel() + chn
;
2732 new_channel
= (uint8_t) chn
;
2735 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Channel
, new_channel
);
2739 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2741 Evoral::MusicalTime new_time
;
2745 if (event
->note()->time() < -delta
) {
2748 new_time
= event
->note()->time() + delta
;
2751 new_time
= event
->note()->time() + delta
;
2757 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2761 MidiRegionView::change_note_length (CanvasNoteEvent
* event
, Evoral::MusicalTime t
)
2763 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, t
);
2767 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2771 if (_selection
.empty()) {
2786 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2787 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2793 start_note_diff_command (_("change velocities"));
2795 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2796 Selection::iterator next
= i
;
2798 change_note_velocity (*i
, delta
, true);
2804 if (!_selection
.empty()) {
2806 snprintf (buf
, sizeof (buf
), "Vel %d",
2807 (int) (*_selection
.begin())->note()->velocity());
2808 show_verbose_cursor (buf
, 10, 10);
2814 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2816 if (_selection
.empty()) {
2833 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2835 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2839 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2846 start_note_diff_command (_("transpose"));
2848 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2849 Selection::iterator next
= i
;
2851 change_note_note (*i
, delta
, true);
2859 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, Evoral::MusicalTime delta
, bool start
, bool end
)
2865 /* grab the current grid distance */
2867 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2869 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2870 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2880 start_note_diff_command (_("change note lengths"));
2882 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2883 Selection::iterator next
= i
;
2886 /* note the negation of the delta for start */
2888 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2897 MidiRegionView::nudge_notes (bool forward
)
2899 if (_selection
.empty()) {
2903 /* pick a note as the point along the timeline to get the nudge distance.
2904 its not necessarily the earliest note, so we may want to pull the notes out
2905 into a vector and sort before using the first one.
2908 framepos_t ref_point
= source_beats_to_absolute_frames ((*(_selection
.begin()))->note()->time());
2910 framepos_t distance
;
2912 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2914 /* grid is off - use nudge distance */
2916 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2922 framepos_t next_pos
= ref_point
;
2925 if (max_framepos
- 1 < next_pos
) {
2929 if (next_pos
== 0) {
2935 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2936 distance
= ref_point
- next_pos
;
2939 if (distance
== 0) {
2943 Evoral::MusicalTime delta
= region_frames_to_region_beats (fabs (distance
));
2949 start_note_diff_command (_("nudge"));
2951 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2952 Selection::iterator next
= i
;
2954 change_note_time (*i
, delta
, true);
2962 MidiRegionView::change_channel(uint8_t channel
)
2964 start_note_diff_command(_("change channel"));
2965 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2966 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::Channel
, channel
);
2974 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2976 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2978 _pre_enter_cursor
= editor
->get_canvas_cursor ();
2980 if (_mouse_state
== SelectTouchDragging
) {
2981 note_selected (ev
, true);
2984 show_verbose_cursor (ev
->note ());
2988 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2990 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2992 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2993 (*i
)->hide_velocity ();
2996 editor
->verbose_cursor()->hide ();
2998 if (_pre_enter_cursor
) {
2999 editor
->set_canvas_cursor (_pre_enter_cursor
);
3000 _pre_enter_cursor
= 0;
3005 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange
* ev
)
3008 /* XXX should get patch name if we can */
3009 s
<< _("Bank:") << (ev
->patch()->bank() + MIDI_BP_ZERO
) << '\n' << _("Program:") << ((int) ev
->patch()->program()) + MIDI_BP_ZERO
<< '\n' << _("Channel:") << ((int) ev
->patch()->channel() + 1);
3010 show_verbose_cursor (s
.str(), 10, 20);
3014 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange
*)
3016 trackview
.editor().verbose_cursor()->hide ();
3020 MidiRegionView::note_mouse_position (float x_fraction
, float /*y_fraction*/, bool can_set_cursor
)
3022 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
3024 if (x_fraction
> 0.0 && x_fraction
< 0.25) {
3025 editor
->set_canvas_cursor (editor
->cursors()->left_side_trim
);
3026 } else if (x_fraction
>= 0.75 && x_fraction
< 1.0) {
3027 editor
->set_canvas_cursor (editor
->cursors()->right_side_trim
);
3029 if (_pre_enter_cursor
&& can_set_cursor
) {
3030 editor
->set_canvas_cursor (_pre_enter_cursor
);
3036 MidiRegionView::set_frame_color()
3040 TimeAxisViewItem::set_frame_color ();
3047 f
= ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
3048 } else if (high_enough_for_name
) {
3049 f
= ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
3054 if (!rect_visible
) {
3055 f
= UINT_RGBA_CHANGE_A (f
, 0);
3058 frame
->property_fill_color_rgba() = f
;
3062 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
3066 case FilterChannels
:
3067 _force_channel
= -1;
3070 _force_channel
= mask
;
3071 mask
= 0xFFFF; // Show all notes as active (below)
3074 // Update notes for selection
3075 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3076 (*i
)->on_channel_selection_change(mask
);
3079 _last_channel_selection
= mask
;
3081 _patch_changes
.clear ();
3082 display_patch_changes ();
3086 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
3088 _model_name
= model
;
3089 _custom_device_mode
= custom_device_mode
;
3094 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
3096 if (_selection
.empty()) {
3100 PublicEditor
& editor (trackview
.editor());
3104 /* XXX what to do ? */
3108 editor
.get_cut_buffer().add (selection_as_cut_buffer());
3116 start_note_diff_command();
3118 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3125 note_diff_remove_note (*i
);
3135 MidiRegionView::selection_as_cut_buffer () const
3139 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3140 NoteType
* n
= (*i
)->note().get();
3141 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
3144 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
3150 /** This method handles undo */
3152 MidiRegionView::paste (framepos_t pos
, float times
, const MidiCutBuffer
& mcb
)
3158 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("MIDI paste @ %1 times %2\n", pos
, times
));
3160 trackview
.session()->begin_reversible_command (_("paste"));
3162 start_note_diff_command (_("paste"));
3164 Evoral::MusicalTime beat_delta
;
3165 Evoral::MusicalTime paste_pos_beats
;
3166 Evoral::MusicalTime duration
;
3167 Evoral::MusicalTime end_point
= 0;
3169 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
3170 paste_pos_beats
= region_frames_to_region_beats (pos
- _region
->position());
3171 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
3172 paste_pos_beats
= 0;
3174 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3175 (*mcb
.notes().begin())->time(),
3176 (*mcb
.notes().rbegin())->end_time(),
3177 duration
, pos
, _region
->position(),
3178 paste_pos_beats
, beat_delta
));
3182 for (int n
= 0; n
< (int) times
; ++n
) {
3184 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
3186 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
3187 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
3189 /* make all newly added notes selected */
3191 note_diff_add_note (copied_note
, true);
3192 end_point
= copied_note
->end_time();
3195 paste_pos_beats
+= duration
;
3198 /* if we pasted past the current end of the region, extend the region */
3200 framepos_t end_frame
= source_beats_to_absolute_frames (end_point
);
3201 framepos_t region_end
= _region
->position() + _region
->length() - 1;
3203 if (end_frame
> region_end
) {
3205 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste extended region from %1 to %2\n", region_end
, end_frame
));
3207 _region
->clear_changes ();
3208 _region
->set_length (end_frame
);
3209 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
3214 trackview
.session()->commit_reversible_command ();
3217 struct EventNoteTimeEarlyFirstComparator
{
3218 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
3219 return a
->note()->time() < b
->note()->time();
3224 MidiRegionView::time_sort_events ()
3226 if (!_sort_needed
) {
3230 EventNoteTimeEarlyFirstComparator cmp
;
3233 _sort_needed
= false;
3237 MidiRegionView::goto_next_note (bool add_to_selection
)
3239 bool use_next
= false;
3241 if (_events
.back()->selected()) {
3245 time_sort_events ();
3247 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3248 if ((*i
)->selected()) {
3251 } else if (use_next
) {
3252 if (!add_to_selection
) {
3255 note_selected (*i
, true, false);
3261 /* use the first one */
3263 unique_select (_events
.front());
3268 MidiRegionView::goto_previous_note (bool add_to_selection
)
3270 bool use_next
= false;
3272 if (_events
.front()->selected()) {
3276 time_sort_events ();
3278 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
3279 if ((*i
)->selected()) {
3282 } else if (use_next
) {
3283 if (!add_to_selection
) {
3286 note_selected (*i
, true, false);
3292 /* use the last one */
3294 unique_select (*(_events
.rbegin()));
3298 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
3300 bool had_selected
= false;
3302 time_sort_events ();
3304 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3305 if ((*i
)->selected()) {
3306 selected
.insert ((*i
)->note());
3307 had_selected
= true;
3311 if (allow_all_if_none_selected
&& !had_selected
) {
3312 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3313 selected
.insert ((*i
)->note());
3319 MidiRegionView::update_ghost_note (double x
, double y
)
3321 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3326 _note_group
->w2i (x
, y
);
3328 PublicEditor
& editor
= trackview
.editor ();
3330 framepos_t
const unsnapped_frame
= editor
.pixel_to_frame (x
);
3332 Evoral::MusicalTime grid_beats
= editor
.get_grid_type_as_beats (success
, unsnapped_frame
);
3338 framecnt_t
const grid_frames
= region_beats_to_region_frames (grid_beats
);
3339 framepos_t f
= snap_frame_to_frame (unsnapped_frame
);
3340 /* use region_frames... because we are converting a delta within the region
3343 double length
= region_frames_to_region_beats (snap_frame_to_frame (f
+ grid_frames
) - f
);
3345 /* note that this sets the time of the ghost note in beats relative to
3346 the start of the region.
3348 _ghost_note
->note()->set_time (region_frames_to_region_beats (f
));
3349 _ghost_note
->note()->set_length (length
);
3350 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
3351 _ghost_note
->note()->set_channel (mtv
->get_channel_for_add ());
3353 /* the ghost note does not appear in ghost regions, so pass false in here */
3354 update_note (_ghost_note
, false);
3356 show_verbose_cursor (_ghost_note
->note ());
3360 MidiRegionView::create_ghost_note (double x
, double y
)
3365 boost::shared_ptr
<NoteType
> g (new NoteType
);
3366 _ghost_note
= new NoEventCanvasNote (*this, *_note_group
, g
);
3367 _ghost_note
->property_outline_color_rgba() = 0x000000aa;
3368 update_ghost_note (x
, y
);
3369 _ghost_note
->show ();
3374 show_verbose_cursor (_ghost_note
->note ());
3378 MidiRegionView::snap_changed ()
3384 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
3388 MidiRegionView::drop_down_keys ()
3390 _mouse_state
= None
;
3394 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double /*x*/, double y
)
3396 double note
= midi_stream_view()->y_to_note(y
);
3398 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3400 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
3402 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
3403 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3404 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
3405 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3410 bool add_mrv_selection
= false;
3412 if (_selection
.empty()) {
3413 add_mrv_selection
= true;
3416 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
3417 if (_selection
.insert (*i
).second
) {
3418 (*i
)->set_selected (true);
3422 if (add_mrv_selection
) {
3423 PublicEditor
& editor (trackview
.editor());
3424 editor
.get_selection().add (this);
3429 MidiRegionView::color_handler ()
3431 RegionView::color_handler ();
3433 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3434 (*i
)->set_selected ((*i
)->selected()); // will change color
3437 /* XXX probably more to do here */
3441 MidiRegionView::enable_display (bool yn
)
3443 RegionView::enable_display (yn
);
3450 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos
)
3452 if (_step_edit_cursor
== 0) {
3453 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
3455 _step_edit_cursor
= new ArdourCanvas::SimpleRect (*group
);
3456 _step_edit_cursor
->property_y1() = 0;
3457 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
3458 _step_edit_cursor
->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3459 _step_edit_cursor
->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3462 move_step_edit_cursor (pos
);
3463 _step_edit_cursor
->show ();
3467 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos
)
3469 _step_edit_cursor_position
= pos
;
3471 if (_step_edit_cursor
) {
3472 double pixel
= trackview
.editor().frame_to_pixel (region_beats_to_region_frames (pos
));
3473 _step_edit_cursor
->property_x1() = pixel
;
3474 set_step_edit_cursor_width (_step_edit_cursor_width
);
3479 MidiRegionView::hide_step_edit_cursor ()
3481 if (_step_edit_cursor
) {
3482 _step_edit_cursor
->hide ();
3487 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats
)
3489 _step_edit_cursor_width
= beats
;
3491 if (_step_edit_cursor
) {
3492 _step_edit_cursor
->property_x2() = _step_edit_cursor
->property_x1() + trackview
.editor().frame_to_pixel (region_beats_to_region_frames (beats
));
3496 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3497 * @param buf Data that has been recorded.
3498 * @param w Source that this data will end up in.
3501 MidiRegionView::data_recorded (boost::shared_ptr
<MidiBuffer
> buf
, boost::weak_ptr
<MidiSource
> w
)
3503 if (!_active_notes
) {
3504 /* we aren't actively being recorded to */
3508 boost::shared_ptr
<MidiSource
> src
= w
.lock ();
3509 if (!src
|| src
!= midi_region()->midi_source()) {
3510 /* recorded data was not destined for our source */
3514 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*> (&trackview
);
3515 BeatsFramesConverter
converter (trackview
.session()->tempo_map(), mtv
->midi_track()->get_capture_start_frame (0));
3517 framepos_t back
= max_framepos
;
3519 for (MidiBuffer::iterator i
= buf
->begin(); i
!= buf
->end(); ++i
) {
3520 Evoral::MIDIEvent
<MidiBuffer::TimeType
> const ev (*i
, false);
3521 assert (ev
.buffer ());
3523 Evoral::MusicalTime
const time_beats
= converter
.from (ev
.time () - converter
.origin_b ());
3525 if (ev
.type() == MIDI_CMD_NOTE_ON
) {
3527 boost::shared_ptr
<NoteType
> note (
3528 new NoteType (ev
.channel(), time_beats
, 0, ev
.note(), ev
.velocity())
3531 add_note (note
, true);
3533 /* fix up our note range */
3534 if (ev
.note() < _current_range_min
) {
3535 midi_stream_view()->apply_note_range (ev
.note(), _current_range_max
, true);
3536 } else if (ev
.note() > _current_range_max
) {
3537 midi_stream_view()->apply_note_range (_current_range_min
, ev
.note(), true);
3540 } else if (ev
.type() == MIDI_CMD_NOTE_OFF
) {
3541 resolve_note (ev
.note (), time_beats
);
3547 midi_stream_view()->check_record_layers (region(), back
);
3551 MidiRegionView::trim_front_starting ()
3553 /* Reparent the note group to the region view's parent, so that it doesn't change
3554 when the region view is trimmed.
3556 _temporary_note_group
= new ArdourCanvas::Group (*group
->property_parent ());
3557 _temporary_note_group
->move (group
->property_x(), group
->property_y());
3558 _note_group
->reparent (*_temporary_note_group
);
3562 MidiRegionView::trim_front_ending ()
3564 _note_group
->reparent (*group
);
3565 delete _temporary_note_group
;
3566 _temporary_note_group
= 0;
3568 if (_region
->start() < 0) {
3569 /* Trim drag made start time -ve; fix this */
3570 midi_region()->fix_negative_start ();
3575 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange
* pc
)
3577 PatchChangeDialog
d (&_source_relative_time_converter
, trackview
.session(), *pc
->patch (), Gtk::Stock::APPLY
);
3578 if (d
.run () != Gtk::RESPONSE_ACCEPT
) {
3582 change_patch_change (pc
->patch(), d
.patch ());
3587 MidiRegionView::show_verbose_cursor (boost::shared_ptr
<NoteType
> n
) const
3590 snprintf (buf
, sizeof (buf
), "%s (%d) Chn %d\nVel %d",
3591 Evoral::midi_note_name (n
->note()).c_str(),
3593 (int) n
->channel() + 1,
3594 (int) n
->velocity());
3596 show_verbose_cursor (buf
, 10, 20);
3600 MidiRegionView::show_verbose_cursor (string
const & text
, double xoffset
, double yoffset
) const
3604 trackview
.editor().get_pointer_position (wx
, wy
);
3609 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3611 double x1
, y1
, x2
, y2
;
3612 trackview
.editor().verbose_cursor()->canvas_item()->get_bounds (x1
, y1
, x2
, y2
);
3614 if ((wy
+ y2
- y1
) > trackview
.editor().canvas_height()) {
3615 wy
-= (y2
- y1
) + 2 * yoffset
;
3618 trackview
.editor().verbose_cursor()->set (text
, wx
, wy
);
3619 trackview
.editor().verbose_cursor()->show ();