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_cut_buffer.h"
58 #include "midi_list_editor.h"
59 #include "midi_region_view.h"
60 #include "midi_streamview.h"
61 #include "midi_time_axis.h"
62 #include "midi_util.h"
63 #include "note_player.h"
64 #include "public_editor.h"
65 #include "rgb_macros.h"
66 #include "selection.h"
67 #include "simpleline.h"
68 #include "streamview.h"
70 #include "mouse_cursors.h"
71 #include "patch_change_dialog.h"
72 #include "verbose_cursor.h"
76 using namespace ARDOUR
;
78 using namespace Editing
;
79 using namespace ArdourCanvas
;
80 using Gtkmm2ext::Keyboard
;
82 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
83 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
const & basic_color
)
84 : RegionView (parent
, tv
, r
, spu
, basic_color
)
86 , _last_channel_selection(0xFFFF)
87 , _current_range_min(0)
88 , _current_range_max(0)
89 , _model_name(string())
90 , _custom_device_mode(string())
92 , _note_group(new ArdourCanvas::Group(*group
))
93 , _note_diff_command (0)
96 , _step_edit_cursor (0)
97 , _step_edit_cursor_width (1.0)
98 , _step_edit_cursor_position (0.0)
99 , _channel_selection_scoped_note (0)
100 , _temporary_note_group (0)
103 , _sort_needed (true)
104 , _optimization_iterator (_events
.end())
106 , _no_sound_notes (false)
109 , _pre_enter_cursor (0)
111 _note_group
->raise_to_top();
112 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
114 connect_to_diskstream ();
117 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
118 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
& basic_color
,
119 TimeAxisViewItem::Visibility visibility
)
120 : RegionView (parent
, tv
, r
, spu
, basic_color
, false, visibility
)
122 , _last_channel_selection(0xFFFF)
123 , _model_name(string())
124 , _custom_device_mode(string())
126 , _note_group(new ArdourCanvas::Group(*parent
))
127 , _note_diff_command (0)
130 , _step_edit_cursor (0)
131 , _step_edit_cursor_width (1.0)
132 , _step_edit_cursor_position (0.0)
133 , _channel_selection_scoped_note (0)
134 , _temporary_note_group (0)
137 , _sort_needed (true)
138 , _optimization_iterator (_events
.end())
140 , _no_sound_notes (false)
144 _note_group
->raise_to_top();
145 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
147 connect_to_diskstream ();
150 MidiRegionView::MidiRegionView (const MidiRegionView
& other
)
151 : sigc::trackable(other
)
154 , _last_channel_selection(0xFFFF)
155 , _model_name(string())
156 , _custom_device_mode(string())
158 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
159 , _note_diff_command (0)
162 , _step_edit_cursor (0)
163 , _step_edit_cursor_width (1.0)
164 , _step_edit_cursor_position (0.0)
165 , _channel_selection_scoped_note (0)
166 , _temporary_note_group (0)
169 , _sort_needed (true)
170 , _optimization_iterator (_events
.end())
172 , _no_sound_notes (false)
179 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
180 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
185 MidiRegionView::MidiRegionView (const MidiRegionView
& other
, boost::shared_ptr
<MidiRegion
> region
)
186 : RegionView (other
, boost::shared_ptr
<Region
> (region
))
188 , _last_channel_selection(0xFFFF)
189 , _model_name(string())
190 , _custom_device_mode(string())
192 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
193 , _note_diff_command (0)
196 , _step_edit_cursor (0)
197 , _step_edit_cursor_width (1.0)
198 , _step_edit_cursor_position (0.0)
199 , _channel_selection_scoped_note (0)
200 , _temporary_note_group (0)
203 , _sort_needed (true)
204 , _optimization_iterator (_events
.end())
206 , _no_sound_notes (false)
213 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
214 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
220 MidiRegionView::init (Gdk::Color
const & basic_color
, bool wfd
)
222 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
224 CanvasNoteEvent::CanvasNoteEventDeleted
.connect (note_delete_connection
, MISSING_INVALIDATOR
,
225 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection
, this, _1
),
229 midi_region()->midi_source(0)->load_model();
232 _model
= midi_region()->midi_source(0)->model();
233 _enable_display
= false;
235 RegionView::init (basic_color
, false);
237 compute_colors (basic_color
);
239 set_height (trackview
.current_height());
242 region_sync_changed ();
243 region_resized (ARDOUR::bounds_change
);
246 reset_width_dependent_items (_pixel_width
);
250 _enable_display
= true;
253 display_model (_model
);
257 group
->raise_to_top();
258 group
->signal_event().connect(
259 sigc::mem_fun(this, &MidiRegionView::canvas_event
), false);
261 midi_view()->signal_channel_mode_changed().connect(
262 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed
));
264 midi_view()->signal_midi_patch_settings_changed().connect(
265 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed
));
267 trackview
.editor().SnapChanged
.connect(snap_changed_connection
, invalidator(*this),
268 ui_bind(&MidiRegionView::snap_changed
, this),
271 connect_to_diskstream ();
275 MidiRegionView::connect_to_diskstream ()
277 midi_view()->midi_track()->DataRecorded
.connect(
278 *this, invalidator(*this),
279 ui_bind(&MidiRegionView::data_recorded
, this, _1
, _2
),
284 MidiRegionView::canvas_event(GdkEvent
* ev
)
287 case GDK_ENTER_NOTIFY
:
288 case GDK_LEAVE_NOTIFY
:
289 _last_event_x
= ev
->crossing
.x
;
290 _last_event_y
= ev
->crossing
.y
;
292 case GDK_MOTION_NOTIFY
:
293 _last_event_x
= ev
->motion
.x
;
294 _last_event_y
= ev
->motion
.y
;
300 if (!trackview
.editor().internal_editing()) {
306 return scroll (&ev
->scroll
);
309 return key_press (&ev
->key
);
311 case GDK_KEY_RELEASE
:
312 return key_release (&ev
->key
);
314 case GDK_BUTTON_PRESS
:
315 return button_press (&ev
->button
);
317 case GDK_2BUTTON_PRESS
:
320 case GDK_BUTTON_RELEASE
:
321 return button_release (&ev
->button
);
323 case GDK_ENTER_NOTIFY
:
324 return enter_notify (&ev
->crossing
);
326 case GDK_LEAVE_NOTIFY
:
327 return leave_notify (&ev
->crossing
);
329 case GDK_MOTION_NOTIFY
:
330 return motion (&ev
->motion
);
340 MidiRegionView::remove_ghost_note ()
347 MidiRegionView::enter_notify (GdkEventCrossing
* ev
)
349 trackview
.editor().MouseModeChanged
.connect (
350 _mouse_mode_connection
, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed
, this), gui_context ()
353 Keyboard::magic_widget_grab_focus();
356 if (trackview
.editor().current_mouse_mode() == MouseRange
) {
357 create_ghost_note (ev
->x
, ev
->y
);
364 MidiRegionView::leave_notify (GdkEventCrossing
*)
366 _mouse_mode_connection
.disconnect ();
368 trackview
.editor().verbose_cursor()->hide ();
369 remove_ghost_note ();
374 MidiRegionView::mouse_mode_changed ()
376 if (trackview
.editor().current_mouse_mode() == MouseRange
&& trackview
.editor().internal_editing()) {
377 create_ghost_note (_last_event_x
, _last_event_y
);
379 remove_ghost_note ();
380 trackview
.editor().verbose_cursor()->hide ();
385 MidiRegionView::button_press (GdkEventButton
* ev
)
387 if (ev
->button
!= 1) {
394 group
->w2i (_last_x
, _last_y
);
396 if (_mouse_state
!= SelectTouchDragging
) {
398 _pressed_button
= ev
->button
;
399 _mouse_state
= Pressed
;
404 _pressed_button
= ev
->button
;
410 MidiRegionView::button_release (GdkEventButton
* ev
)
412 double event_x
, event_y
;
413 framepos_t event_frame
= 0;
415 if (ev
->button
!= 1) {
422 group
->w2i(event_x
, event_y
);
423 group
->ungrab(ev
->time
);
425 event_frame
= trackview
.editor().pixel_to_frame(event_x
);
427 switch (_mouse_state
) {
428 case Pressed
: // Clicked
430 switch (trackview
.editor().current_mouse_mode()) {
436 if (Keyboard::is_insert_note_event(ev
)){
438 double event_x
, event_y
;
442 group
->w2i(event_x
, event_y
);
445 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
451 create_note_at (event_x
, event_y
, beats
, true);
459 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
465 create_note_at (event_x
, event_y
, beats
, true);
476 case SelectRectDragging
: // Select drag done
483 case AddDragging
: // Add drag done
487 if (Keyboard::is_insert_note_event(ev
) || trackview
.editor().current_mouse_mode() == MouseRange
){
489 if (_drag_rect
->property_x2() > _drag_rect
->property_x1() + 2) {
491 const double x
= _drag_rect
->property_x1();
492 const double length
= trackview
.editor().pixel_to_frame (_drag_rect
->property_x2() - _drag_rect
->property_x1());
494 create_note_at (x
, _drag_rect
->property_y1(), frames_to_beats(length
), true);
501 create_ghost_note (ev
->x
, ev
->y
);
511 MidiRegionView::motion (GdkEventMotion
* ev
)
513 double event_x
, event_y
;
514 framepos_t event_frame
= 0;
518 group
->w2i(event_x
, event_y
);
520 // convert event_x to global frame
521 event_frame
= snap_pixel_to_frame (event_x
);
523 if (!_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
524 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())
525 && _mouse_state
!= AddDragging
){
527 create_ghost_note (ev
->x
, ev
->y
);
529 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
530 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())){
532 update_ghost_note (ev
->x
, ev
->y
);
534 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
){
539 trackview
.editor().verbose_cursor()->hide ();
541 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() == MouseRange
) {
542 update_ghost_note (ev
->x
, ev
->y
);
545 /* any motion immediately hides velocity text that may have been visible */
547 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
548 (*i
)->hide_velocity ();
551 switch (_mouse_state
) {
552 case Pressed
: // Maybe start a drag, if we've moved a bit
554 if (fabs (event_x
- _last_x
) < 1 && fabs (event_y
- _last_y
) < 1) {
555 /* no appreciable movement since the button was pressed */
560 if (_pressed_button
== 1 && trackview
.editor().current_mouse_mode() == MouseObject
561 && !Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())) {
563 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
564 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
568 _drag_start_x
= event_x
;
569 _drag_start_y
= event_y
;
571 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
572 _drag_rect
->property_x1() = event_x
;
573 _drag_rect
->property_y1() = event_y
;
574 _drag_rect
->property_x2() = event_x
;
575 _drag_rect
->property_y2() = event_y
;
576 _drag_rect
->property_outline_what() = 0xFF;
577 _drag_rect
->property_outline_color_rgba()
578 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
579 _drag_rect
->property_fill_color_rgba()
580 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
582 _mouse_state
= SelectRectDragging
;
585 // Add note drag start
586 } else if (trackview
.editor().internal_editing()) {
591 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
592 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
596 _drag_start_x
= event_x
;
597 _drag_start_y
= event_y
;
599 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
600 _drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
602 _drag_rect
->property_y1() = midi_stream_view()->note_to_y(
603 midi_stream_view()->y_to_note(event_y
));
604 _drag_rect
->property_x2() = trackview
.editor().frame_to_pixel(event_frame
);
605 _drag_rect
->property_y2() = _drag_rect
->property_y1()
606 + floor(midi_stream_view()->note_height());
607 _drag_rect
->property_outline_what() = 0xFF;
608 _drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
609 _drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
611 _mouse_state
= AddDragging
;
618 trackview
.editor().verbose_cursor()->hide ();
626 case SelectRectDragging
: // Select drag motion
627 case AddDragging
: // Add note drag motion
632 GdkModifierType state
;
633 gdk_window_get_pointer(ev
->window
, &t_x
, &t_y
, &state
);
638 if (_mouse_state
== AddDragging
){
639 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
644 if (event_x
> _drag_start_x
){
645 _drag_rect
->property_x2() = event_x
;
648 _drag_rect
->property_x1() = event_x
;
652 if (_drag_rect
&& _mouse_state
== SelectRectDragging
) {
654 if (event_y
> _drag_start_y
){
655 _drag_rect
->property_y2() = event_y
;
658 _drag_rect
->property_y1() = event_y
;
661 update_drag_selection(_drag_start_x
, event_x
, _drag_start_y
, event_y
);
667 case SelectTouchDragging
:
679 MidiRegionView::scroll (GdkEventScroll
* ev
)
681 if (_selection
.empty()) {
685 trackview
.editor().verbose_cursor()->hide ();
687 bool fine
= !Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
);
689 if (ev
->direction
== GDK_SCROLL_UP
) {
690 change_velocities (true, fine
, false);
691 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
692 change_velocities (false, fine
, false);
698 MidiRegionView::key_press (GdkEventKey
* ev
)
700 /* since GTK bindings are generally activated on press, and since
701 detectable auto-repeat is the name of the game and only sends
702 repeated presses, carry out key actions at key press, not release.
705 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
){
706 _mouse_state
= SelectTouchDragging
;
709 } else if (ev
->keyval
== GDK_Escape
) {
713 } else if (ev
->keyval
== GDK_comma
|| ev
->keyval
== GDK_period
) {
715 bool start
= (ev
->keyval
== GDK_comma
);
716 bool end
= (ev
->keyval
== GDK_period
);
717 bool shorter
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
);
718 bool fine
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
720 change_note_lengths (fine
, shorter
, 0.0, start
, end
);
724 } else if (ev
->keyval
== GDK_Delete
) {
729 } else if (ev
->keyval
== GDK_Tab
) {
731 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
732 goto_previous_note ();
738 } else if (ev
->keyval
== GDK_Up
) {
740 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
741 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
743 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
744 change_velocities (true, fine
, allow_smush
);
746 transpose (true, fine
, allow_smush
);
750 } else if (ev
->keyval
== GDK_Down
) {
752 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
753 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
755 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
756 change_velocities (false, fine
, allow_smush
);
758 transpose (false, fine
, allow_smush
);
762 } else if (ev
->keyval
== GDK_Left
) {
767 } else if (ev
->keyval
== GDK_Right
) {
772 } else if (ev
->keyval
== GDK_Control_L
) {
781 MidiRegionView::key_release (GdkEventKey
* ev
)
783 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
791 MidiRegionView::show_list_editor ()
794 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
796 _list_editor
->present ();
799 /** Add a note to the model, and the view, at a canvas (click) coordinate.
800 * \param x horizontal position in pixels
801 * \param y vertical position in pixels
802 * \param length duration of the note in beats, which will be snapped to the grid
803 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
806 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
808 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
809 MidiStreamView
* const view
= mtv
->midi_view();
811 double note
= view
->y_to_note(y
);
814 assert(note
<= 127.0);
816 // Start of note in frames relative to region start
817 framepos_t
const start_frames
= snap_pixel_to_frame (x
);
818 assert(start_frames
>= 0);
821 length
= frames_to_beats(
822 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
824 assert (length
!= 0);
827 length
= frames_to_beats (beats_to_frames (length
) - 1);
830 const boost::shared_ptr
<NoteType
> new_note (new NoteType (mtv
->get_channel_for_add (),
831 frames_to_beats(start_frames
+ _region
->start()), length
,
832 (uint8_t)note
, 0x40));
834 if (_model
->contains (new_note
)) {
838 view
->update_note_range(new_note
->note());
840 MidiModel::NoteDiffCommand
* cmd
= _model
->new_note_diff_command("add note");
842 _model
->apply_command(*trackview
.session(), cmd
);
844 play_midi_note (new_note
);
848 MidiRegionView::clear_events()
853 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
854 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
859 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
864 _patch_changes
.clear();
866 _optimization_iterator
= _events
.end();
870 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
874 content_connection
.disconnect ();
875 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
879 if (_enable_display
) {
885 MidiRegionView::start_note_diff_command (string name
)
887 if (!_note_diff_command
) {
888 _note_diff_command
= _model
->new_note_diff_command (name
);
893 MidiRegionView::note_diff_add_note (const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
895 if (_note_diff_command
) {
896 _note_diff_command
->add (note
);
899 _marked_for_selection
.insert(note
);
902 _marked_for_velocity
.insert(note
);
907 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent
* ev
)
909 if (_note_diff_command
&& ev
->note()) {
910 _note_diff_command
->remove(ev
->note());
915 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
916 MidiModel::NoteDiffCommand::Property property
,
919 if (_note_diff_command
) {
920 _note_diff_command
->change (ev
->note(), property
, val
);
925 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
926 MidiModel::NoteDiffCommand::Property property
,
927 Evoral::MusicalTime val
)
929 if (_note_diff_command
) {
930 _note_diff_command
->change (ev
->note(), property
, val
);
935 MidiRegionView::apply_diff (bool as_subcommand
)
939 if (!_note_diff_command
) {
943 if ((add_or_remove
= _note_diff_command
->adds_or_removes())) {
944 // Mark all selected notes for selection when model reloads
945 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
946 _marked_for_selection
.insert((*i
)->note());
951 _model
->apply_command_as_subcommand (*trackview
.session(), _note_diff_command
);
953 _model
->apply_command (*trackview
.session(), _note_diff_command
);
956 _note_diff_command
= 0;
957 midi_view()->midi_track()->playlist_modified();
960 _marked_for_selection
.clear();
963 _marked_for_velocity
.clear();
967 MidiRegionView::abort_command()
969 delete _note_diff_command
;
970 _note_diff_command
= 0;
975 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
977 if (_optimization_iterator
!= _events
.end()) {
978 ++_optimization_iterator
;
981 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
982 return *_optimization_iterator
;
985 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
986 if ((*_optimization_iterator
)->note() == note
) {
987 return *_optimization_iterator
;
995 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
997 MidiModel::Notes notes
;
998 _model
->get_notes (notes
, op
, val
, chan_mask
);
1000 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1001 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
1009 MidiRegionView::redisplay_model()
1011 // Don't redisplay the model if we're currently recording and displaying that
1012 if (_active_notes
) {
1020 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1021 (*i
)->invalidate ();
1024 MidiModel::ReadLock
lock(_model
->read_lock());
1026 MidiModel::Notes
& notes (_model
->notes());
1027 _optimization_iterator
= _events
.begin();
1029 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1031 boost::shared_ptr
<NoteType
> note (*n
);
1032 CanvasNoteEvent
* cne
;
1035 if (note_in_region_range (note
, visible
)) {
1037 if ((cne
= find_canvas_note (note
)) != 0) {
1044 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
1046 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
1058 add_note (note
, visible
);
1063 if ((cne
= find_canvas_note (note
)) != 0) {
1071 /* remove note items that are no longer valid */
1073 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
1074 if (!(*i
)->valid ()) {
1076 i
= _events
.erase (i
);
1082 _patch_changes
.clear();
1086 display_patch_changes ();
1088 _marked_for_selection
.clear ();
1089 _marked_for_velocity
.clear ();
1091 /* we may have caused _events to contain things out of order (e.g. if a note
1092 moved earlier or later). we don't generally need them in time order, but
1093 make a note that a sort is required for those cases that require it.
1096 _sort_needed
= true;
1100 MidiRegionView::display_patch_changes ()
1102 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1103 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
1105 for (uint8_t i
= 0; i
< 16; ++i
) {
1106 if (chn_mask
& (1<<i
)) {
1107 display_patch_changes_on_channel (i
);
1113 MidiRegionView::display_patch_changes_on_channel (uint8_t channel
)
1115 for (MidiModel::PatchChanges::const_iterator i
= _model
->patch_changes().begin(); i
!= _model
->patch_changes().end(); ++i
) {
1117 if ((*i
)->channel() != channel
) {
1121 MIDI::Name::PatchPrimaryKey
patch_key ((*i
)->bank_msb (), (*i
)->bank_lsb (), (*i
)->program ());
1123 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1124 MIDI::Name::MidiPatchManager::instance().find_patch(
1125 _model_name
, _custom_device_mode
, channel
, patch_key
);
1128 add_canvas_patch_change (*i
, patch
->name());
1131 /* program and bank numbers are zero-based: convert to one-based */
1132 snprintf (buf
, 16, "%d\n%d", (*i
)->program() + 1, (*i
)->bank() + 1);
1133 add_canvas_patch_change (*i
, buf
);
1139 MidiRegionView::display_sysexes()
1141 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1142 Evoral::MusicalTime time
= (*i
)->time();
1147 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1148 str
<< int((*i
)->buffer()[b
]);
1149 if (b
!= (*i
)->size() -1) {
1153 string text
= str
.str();
1155 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1157 double height
= midi_stream_view()->contents_height();
1159 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1160 new CanvasSysEx(*this, *_note_group
, text
, height
, x
, 1.0));
1162 // Show unless patch change is beyond the region bounds
1163 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1169 _sys_exes
.push_back(sysex
);
1174 MidiRegionView::~MidiRegionView ()
1176 in_destructor
= true;
1178 trackview
.editor().verbose_cursor()->hide ();
1180 note_delete_connection
.disconnect ();
1182 delete _list_editor
;
1184 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1186 if (_active_notes
) {
1194 delete _note_diff_command
;
1195 delete _step_edit_cursor
;
1196 delete _temporary_note_group
;
1200 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1202 RegionView::region_resized(what_changed
);
1204 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1205 set_duration(_region
->length(), 0);
1206 if (_enable_display
) {
1213 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1215 RegionView::reset_width_dependent_items(pixel_width
);
1216 assert(_pixel_width
== pixel_width
);
1218 if (_enable_display
) {
1222 move_step_edit_cursor (_step_edit_cursor_position
);
1223 set_step_edit_cursor_width (_step_edit_cursor_width
);
1227 MidiRegionView::set_height (double height
)
1229 static const double FUDGE
= 2.0;
1230 const double old_height
= _height
;
1231 RegionView::set_height(height
);
1232 _height
= height
- FUDGE
;
1234 apply_note_range(midi_stream_view()->lowest_note(),
1235 midi_stream_view()->highest_note(),
1236 height
!= old_height
+ FUDGE
);
1239 name_pixbuf
->raise_to_top();
1242 for (PatchChanges::iterator x
= _patch_changes
.begin(); x
!= _patch_changes
.end(); ++x
) {
1243 (*x
)->set_height (midi_stream_view()->contents_height());
1246 if (_step_edit_cursor
) {
1247 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
1252 /** Apply the current note range from the stream view
1253 * by repositioning/hiding notes as necessary
1256 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1258 if (!_enable_display
) {
1262 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1266 _current_range_min
= min
;
1267 _current_range_max
= max
;
1269 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1270 CanvasNoteEvent
* event
= *i
;
1271 boost::shared_ptr
<NoteType
> note (event
->note());
1273 if (note
->note() < _current_range_min
||
1274 note
->note() > _current_range_max
) {
1280 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1282 const double y1
= midi_stream_view()->note_to_y(note
->note());
1283 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1285 cnote
->property_y1() = y1
;
1286 cnote
->property_y2() = y2
;
1288 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1290 const double diamond_size
= update_hit (chit
);
1292 chit
->set_height (diamond_size
);
1298 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1302 double unit_position
= _region
->position () / samples_per_unit
;
1303 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1304 MidiGhostRegion
* ghost
;
1306 if (mtv
&& mtv
->midi_view()) {
1307 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1308 to allow having midi notes on top of note lines and waveforms.
1310 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1312 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1315 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1316 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1317 ghost
->add_note(note
);
1321 ghost
->set_height ();
1322 ghost
->set_duration (_region
->length() / samples_per_unit
);
1323 ghosts
.push_back (ghost
);
1325 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1331 /** Begin tracking note state for successive calls to add_event
1334 MidiRegionView::begin_write()
1336 assert(!_active_notes
);
1337 _active_notes
= new CanvasNote
*[128];
1338 for (unsigned i
=0; i
< 128; ++i
) {
1339 _active_notes
[i
] = 0;
1344 /** Destroy note state for add_event
1347 MidiRegionView::end_write()
1349 delete[] _active_notes
;
1351 _marked_for_selection
.clear();
1352 _marked_for_velocity
.clear();
1356 /** Resolve an active MIDI note (while recording).
1359 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1361 if (midi_view()->note_mode() != Sustained
) {
1365 if (_active_notes
&& _active_notes
[note
]) {
1367 const framepos_t end_time_frames
= beats_to_frames(end_time
);
1369 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1370 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1371 _active_notes
[note
] = 0;
1376 /** Extend active notes to rightmost edge of region (if length is changed)
1379 MidiRegionView::extend_active_notes()
1381 if (!_active_notes
) {
1385 for (unsigned i
=0; i
< 128; ++i
) {
1386 if (_active_notes
[i
]) {
1387 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1394 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1396 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1400 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1402 if (!route_ui
|| !route_ui
->midi_track()) {
1406 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1412 MidiRegionView::play_midi_chord (vector
<boost::shared_ptr
<NoteType
> > notes
)
1414 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1418 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1420 if (!route_ui
|| !route_ui
->midi_track()) {
1424 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1426 for (vector
<boost::shared_ptr
<NoteType
> >::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1435 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1437 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1439 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1440 (note_start_frames
< _region
->start());
1442 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1443 (note
->note() <= midi_stream_view()->highest_note());
1448 /** Update a canvas note's size from its model note.
1449 * @param ev Canvas note to update.
1450 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1453 MidiRegionView::update_note (CanvasNote
* ev
, bool update_ghost_regions
)
1455 boost::shared_ptr
<NoteType
> note
= ev
->note();
1457 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1459 /* trim note display to not overlap the end of its region */
1460 const framepos_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1462 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1463 const double y1
= midi_stream_view()->note_to_y(note
->note());
1464 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1466 ev
->property_x1() = x
;
1467 ev
->property_y1() = y1
;
1469 if (note
->length() > 0) {
1470 ev
->property_x2() = note_endpixel
;
1472 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1475 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1477 if (note
->length() == 0) {
1478 if (_active_notes
) {
1479 assert(note
->note() < 128);
1480 // If this note is already active there's a stuck note,
1481 // finish the old note rectangle
1482 if (_active_notes
[note
->note()]) {
1483 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1484 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1485 old_rect
->property_x2() = x
;
1486 old_rect
->property_outline_what() = (guint32
) 0xF;
1488 _active_notes
[note
->note()] = ev
;
1490 /* outline all but right edge */
1491 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1493 /* outline all edges */
1494 ev
->property_outline_what() = (guint32
) 0xF;
1497 if (update_ghost_regions
) {
1498 for (std::vector
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
1499 MidiGhostRegion
* gr
= dynamic_cast<MidiGhostRegion
*> (*i
);
1501 gr
->update_note (ev
);
1508 MidiRegionView::update_hit (CanvasHit
* ev
)
1510 boost::shared_ptr
<NoteType
> note
= ev
->note();
1512 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1513 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1514 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1515 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1519 return diamond_size
;
1522 /** Add a MIDI note to the view (with length).
1524 * If in sustained mode, notes with length 0 will be considered active
1525 * notes, and resolve_note should be called when the corresponding note off
1526 * event arrives, to properly display the note.
1529 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1531 CanvasNoteEvent
* event
= 0;
1533 assert(note
->time() >= 0);
1534 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1536 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1538 if (midi_view()->note_mode() == Sustained
) {
1540 CanvasNote
* ev_rect
= new CanvasNote(*this, *_note_group
, note
);
1542 update_note (ev_rect
);
1546 MidiGhostRegion
* gr
;
1548 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1549 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1550 gr
->add_note(ev_rect
);
1554 } else if (midi_view()->note_mode() == Percussive
) {
1556 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1558 CanvasHit
* ev_diamond
= new CanvasHit(*this, *_note_group
, diamond_size
, note
);
1560 update_hit (ev_diamond
);
1569 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1570 note_selected(event
, true);
1573 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1574 event
->show_velocity();
1577 event
->on_channel_selection_change(_last_channel_selection
);
1578 _events
.push_back(event
);
1587 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1588 MidiStreamView
* const view
= mtv
->midi_view();
1590 view
->update_note_range(note
->note());
1594 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1595 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1597 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1599 /* potentially extend region to hold new note */
1601 framepos_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1602 framepos_t region_end
= _region
->position() + _region
->length() - 1;
1604 if (end_frame
> region_end
) {
1605 _region
->set_length (end_frame
- _region
->position(), this);
1608 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1609 MidiStreamView
* const view
= mtv
->midi_view();
1611 view
->update_note_range(new_note
->note());
1613 _marked_for_selection
.clear ();
1616 start_note_diff_command (_("step add"));
1617 note_diff_add_note (new_note
, true, false);
1620 // last_step_edit_note = new_note;
1624 MidiRegionView::step_sustain (Evoral::MusicalTime beats
)
1626 change_note_lengths (false, false, beats
, false, true);
1630 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch
, const string
& displaytext
)
1632 assert (patch
->time() >= 0);
1634 const double x
= trackview
.editor().frame_to_pixel (beats_to_frames (patch
->time()));
1636 double const height
= midi_stream_view()->contents_height();
1638 boost::shared_ptr
<CanvasPatchChange
> patch_change
= boost::shared_ptr
<CanvasPatchChange
>(
1639 new CanvasPatchChange(*this, *_note_group
,
1644 _custom_device_mode
,
1648 // Show unless patch change is beyond the region bounds
1649 if (patch
->time() - _region
->start() >= _region
->length() || patch
->time() < _region
->start()) {
1650 patch_change
->hide();
1652 patch_change
->show();
1655 _patch_changes
.push_back (patch_change
);
1659 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1661 MidiModel::PatchChanges::iterator i
= _model
->patch_change_lower_bound (time
);
1662 while (i
!= _model
->patch_changes().end() && (*i
)->channel() != channel
) {
1666 if (i
!= _model
->patch_changes().end()) {
1667 key
.msb
= (*i
)->bank_msb ();
1668 key
.lsb
= (*i
)->bank_lsb ();
1669 key
.program_number
= (*i
)->program ();
1671 key
.msb
= key
.lsb
= key
.program_number
= 0;
1674 assert (key
.is_sane());
1679 MidiRegionView::change_patch_change (CanvasPatchChange
& pc
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1681 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1683 if (pc
.patch()->program() != new_patch
.program_number
) {
1684 c
->change_program (pc
.patch (), new_patch
.program_number
);
1687 int const new_bank
= (new_patch
.msb
<< 7) | new_patch
.lsb
;
1688 if (pc
.patch()->bank() != new_bank
) {
1689 c
->change_bank (pc
.patch (), new_bank
);
1692 _model
->apply_command (*trackview
.session(), c
);
1694 _patch_changes
.clear ();
1695 display_patch_changes ();
1699 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change
, const Evoral::PatchChange
<Evoral::MusicalTime
> & new_change
)
1701 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1703 if (old_change
->time() != new_change
.time()) {
1704 c
->change_time (old_change
, new_change
.time());
1707 if (old_change
->channel() != new_change
.channel()) {
1708 c
->change_channel (old_change
, new_change
.channel());
1711 if (old_change
->program() != new_change
.program()) {
1712 c
->change_program (old_change
, new_change
.program());
1715 if (old_change
->bank() != new_change
.bank()) {
1716 c
->change_bank (old_change
, new_change
.bank());
1719 _model
->apply_command (*trackview
.session(), c
);
1721 _patch_changes
.clear ();
1722 display_patch_changes ();
1725 /** Add a patch change to the region.
1726 * @param t Time in frames relative to region position
1727 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1728 * MidiTimeAxisView::get_channel_for_add())
1731 MidiRegionView::add_patch_change (framecnt_t t
, Evoral::PatchChange
<Evoral::MusicalTime
> const & patch
)
1733 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1735 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("add patch change"));
1736 c
->add (MidiModel::PatchChangePtr (
1737 new Evoral::PatchChange
<Evoral::MusicalTime
> (
1738 frames_to_beats (t
+ midi_region()->start()), mtv
->get_channel_for_add(), patch
.program(), patch
.bank()
1742 _model
->apply_command (*trackview
.session(), c
);
1744 _patch_changes
.clear ();
1745 display_patch_changes ();
1749 MidiRegionView::move_patch_change (CanvasPatchChange
& pc
, Evoral::MusicalTime t
)
1751 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("move patch change"));
1752 c
->change_time (pc
.patch (), t
);
1753 _model
->apply_command (*trackview
.session(), c
);
1755 _patch_changes
.clear ();
1756 display_patch_changes ();
1760 MidiRegionView::delete_patch_change (CanvasPatchChange
* pc
)
1762 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("delete patch change"));
1763 c
->remove (pc
->patch ());
1764 _model
->apply_command (*trackview
.session(), c
);
1766 _patch_changes
.clear ();
1767 display_patch_changes ();
1771 MidiRegionView::previous_patch (CanvasPatchChange
& patch
)
1773 if (patch
.patch()->program() < 127) {
1774 MIDI::Name::PatchPrimaryKey key
;
1775 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1776 key
.program_number
++;
1777 change_patch_change (patch
, key
);
1782 MidiRegionView::next_patch (CanvasPatchChange
& patch
)
1784 if (patch
.patch()->program() > 0) {
1785 MIDI::Name::PatchPrimaryKey key
;
1786 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1787 key
.program_number
--;
1788 change_patch_change (patch
, key
);
1793 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1795 if (_selection
.empty()) {
1799 if (_selection
.erase (cne
) > 0) {
1800 cerr
<< "Erased a CNE from selection\n";
1805 MidiRegionView::delete_selection()
1807 if (_selection
.empty()) {
1811 start_note_diff_command (_("delete selection"));
1813 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1814 if ((*i
)->selected()) {
1815 _note_diff_command
->remove((*i
)->note());
1825 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1827 start_note_diff_command (_("delete note"));
1828 _note_diff_command
->remove (n
);
1831 trackview
.editor().verbose_cursor()->hide ();
1835 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1837 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1838 if ((*i
)->selected() && (*i
) != ev
) {
1839 (*i
)->set_selected(false);
1840 (*i
)->hide_velocity();
1848 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1850 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1853 Selection::iterator tmp
= i
;
1856 (*i
)->set_selected (false);
1857 _selection
.erase (i
);
1866 /* don't bother with removing this regionview from the editor selection,
1867 since we're about to add another note, and thus put/keep this
1868 regionview in the editor selection.
1871 if (!ev
->selected()) {
1872 add_to_selection (ev
);
1877 MidiRegionView::select_all_notes ()
1881 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1882 add_to_selection (*i
);
1887 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1889 uint8_t low_note
= 127;
1890 uint8_t high_note
= 0;
1891 MidiModel::Notes
& notes (_model
->notes());
1892 _optimization_iterator
= _events
.begin();
1898 if (extend
&& _selection
.empty()) {
1904 /* scan existing selection to get note range */
1906 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1907 if ((*i
)->note()->note() < low_note
) {
1908 low_note
= (*i
)->note()->note();
1910 if ((*i
)->note()->note() > high_note
) {
1911 high_note
= (*i
)->note()->note();
1915 low_note
= min (low_note
, notenum
);
1916 high_note
= max (high_note
, notenum
);
1919 _no_sound_notes
= true;
1921 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1923 boost::shared_ptr
<NoteType
> note (*n
);
1924 CanvasNoteEvent
* cne
;
1925 bool select
= false;
1927 if (((1 << note
->channel()) & channel_mask
) != 0) {
1929 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1932 } else if (note
->note() == notenum
) {
1938 if ((cne
= find_canvas_note (note
)) != 0) {
1939 // extend is false because we've taken care of it,
1940 // since it extends by time range, not pitch.
1941 note_selected (cne
, add
, false);
1945 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1949 _no_sound_notes
= false;
1953 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
1955 MidiModel::Notes
& notes (_model
->notes());
1956 _optimization_iterator
= _events
.begin();
1958 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1960 boost::shared_ptr
<NoteType
> note (*n
);
1961 CanvasNoteEvent
* cne
;
1963 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
1964 if ((cne
= find_canvas_note (note
)) != 0) {
1965 if (cne
->selected()) {
1966 note_deselected (cne
);
1968 note_selected (cne
, true, false);
1976 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
1979 clear_selection_except(ev
);
1984 if (!ev
->selected()) {
1985 add_to_selection (ev
);
1989 /* find end of latest note selected, select all between that and the start of "ev" */
1991 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
1992 Evoral::MusicalTime latest
= 0;
1994 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1995 if ((*i
)->note()->end_time() > latest
) {
1996 latest
= (*i
)->note()->end_time();
1998 if ((*i
)->note()->time() < earliest
) {
1999 earliest
= (*i
)->note()->time();
2003 if (ev
->note()->end_time() > latest
) {
2004 latest
= ev
->note()->end_time();
2007 if (ev
->note()->time() < earliest
) {
2008 earliest
= ev
->note()->time();
2011 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2013 /* find notes entirely within OR spanning the earliest..latest range */
2015 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
2016 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
2017 add_to_selection (*i
);
2021 /* if events were guaranteed to be time sorted, we could do this.
2022 but as of sept 10th 2009, they no longer are.
2025 if ((*i
)->note()->time() > latest
) {
2034 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
2036 remove_from_selection (ev
);
2040 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
2050 // TODO: Make this faster by storing the last updated selection rect, and only
2051 // adjusting things that are in the area that appears/disappeared.
2052 // We probably need a tree to be able to find events in O(log(n)) time.
2054 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2056 /* check if any corner of the note is inside the rect
2059 1) this is computing "touched by", not "contained by" the rect.
2060 2) this does not require that events be sorted in time.
2063 const double ix1
= (*i
)->x1();
2064 const double ix2
= (*i
)->x2();
2065 const double iy1
= (*i
)->y1();
2066 const double iy2
= (*i
)->y2();
2068 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2069 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
2070 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2071 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
2074 if (!(*i
)->selected()) {
2075 add_to_selection (*i
);
2077 } else if ((*i
)->selected()) {
2078 // Not inside rectangle
2079 remove_from_selection (*i
);
2085 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
2087 Selection::iterator i
= _selection
.find (ev
);
2089 if (i
!= _selection
.end()) {
2090 _selection
.erase (i
);
2093 ev
->set_selected (false);
2094 ev
->hide_velocity ();
2096 if (_selection
.empty()) {
2097 PublicEditor
& editor (trackview
.editor());
2098 editor
.get_selection().remove (this);
2103 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
2105 bool add_mrv_selection
= false;
2107 if (_selection
.empty()) {
2108 add_mrv_selection
= true;
2111 if (_selection
.insert (ev
).second
) {
2112 ev
->set_selected (true);
2113 play_midi_note ((ev
)->note());
2116 if (add_mrv_selection
) {
2117 PublicEditor
& editor (trackview
.editor());
2118 editor
.get_selection().add (this);
2123 MidiRegionView::move_selection(double dx
, double dy
, double cumulative_dy
)
2125 typedef vector
<boost::shared_ptr
<NoteType
> > PossibleChord
;
2126 PossibleChord to_play
;
2127 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2129 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2130 if ((*i
)->note()->time() < earliest
) {
2131 earliest
= (*i
)->note()->time();
2135 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2136 if (Evoral::musical_time_equal ((*i
)->note()->time(), earliest
)) {
2137 to_play
.push_back ((*i
)->note());
2139 (*i
)->move_event(dx
, dy
);
2142 if (dy
&& !_selection
.empty() && !_no_sound_notes
&& trackview
.editor().sound_notes()) {
2144 if (to_play
.size() > 1) {
2146 PossibleChord shifted
;
2148 for (PossibleChord::iterator n
= to_play
.begin(); n
!= to_play
.end(); ++n
) {
2149 boost::shared_ptr
<NoteType
> moved_note (new NoteType (**n
));
2150 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2151 shifted
.push_back (moved_note
);
2154 play_midi_chord (shifted
);
2156 } else if (!to_play
.empty()) {
2158 boost::shared_ptr
<NoteType
> moved_note (new NoteType (*to_play
.front()));
2159 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2160 play_midi_note (moved_note
);
2166 MidiRegionView::note_dropped(CanvasNoteEvent
*, frameoffset_t dt
, int8_t dnote
)
2168 assert (!_selection
.empty());
2170 uint8_t lowest_note_in_selection
= 127;
2171 uint8_t highest_note_in_selection
= 0;
2172 uint8_t highest_note_difference
= 0;
2174 // find highest and lowest notes first
2176 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2177 uint8_t pitch
= (*i
)->note()->note();
2178 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
2179 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
2183 cerr << "dnote: " << (int) dnote << endl;
2184 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2185 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2186 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2187 << int(highest_note_in_selection) << endl;
2188 cerr << "selection size: " << _selection.size() << endl;
2189 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2192 // Make sure the note pitch does not exceed the MIDI standard range
2193 if (highest_note_in_selection
+ dnote
> 127) {
2194 highest_note_difference
= highest_note_in_selection
- 127;
2197 start_note_diff_command (_("move notes"));
2199 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
2201 Evoral::MusicalTime new_time
= frames_to_beats (beats_to_frames ((*i
)->note()->time()) + dt
);
2207 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2209 uint8_t original_pitch
= (*i
)->note()->note();
2210 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
2212 // keep notes in standard midi range
2213 clamp_to_0_127(new_pitch
);
2215 // keep original pitch if note is dragged outside valid midi range
2216 if ((original_pitch
!= 0 && new_pitch
== 0)
2217 || (original_pitch
!= 127 && new_pitch
== 127)) {
2218 new_pitch
= original_pitch
;
2221 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2222 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2224 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::NoteNumber
, new_pitch
);
2229 // care about notes being moved beyond the upper/lower bounds on the canvas
2230 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2231 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2232 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2237 MidiRegionView::snap_pixel_to_frame(double x
)
2239 PublicEditor
& editor (trackview
.editor());
2240 return snap_frame_to_frame (editor
.pixel_to_frame (x
));
2243 /** Snap a frame offset within our region using the current snap settings.
2244 * @param x Frame offset from this region's position.
2245 * @return Snapped frame offset from this region's position.
2248 MidiRegionView::snap_frame_to_frame (frameoffset_t x
)
2250 PublicEditor
& editor
= trackview
.editor();
2252 /* x is region relative, convert it to global absolute frames */
2253 framepos_t
const session_frame
= x
+ _region
->position();
2255 /* try a snap in either direction */
2256 framepos_t frame
= session_frame
;
2257 editor
.snap_to (frame
, 0);
2259 /* if we went off the beginning of the region, snap forwards */
2260 if (frame
< _region
->position ()) {
2261 frame
= session_frame
;
2262 editor
.snap_to (frame
, 1);
2265 /* back to region relative */
2266 return frame
- _region
->position();
2270 MidiRegionView::snap_to_pixel(double x
)
2272 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2276 MidiRegionView::get_position_pixels()
2278 framepos_t region_frame
= get_position();
2279 return trackview
.editor().frame_to_pixel(region_frame
);
2283 MidiRegionView::get_end_position_pixels()
2285 framepos_t frame
= get_position() + get_duration ();
2286 return trackview
.editor().frame_to_pixel(frame
);
2290 MidiRegionView::beats_to_frames(double beats
) const
2292 return _time_converter
.to(beats
);
2296 MidiRegionView::frames_to_beats(framepos_t frames
) const
2298 return _time_converter
.from(frames
);
2302 MidiRegionView::begin_resizing (bool /*at_front*/)
2304 _resize_data
.clear();
2306 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2307 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2309 // only insert CanvasNotes into the map
2311 NoteResizeData
*resize_data
= new NoteResizeData();
2312 resize_data
->canvas_note
= note
;
2314 // create a new SimpleRect from the note which will be the resize preview
2315 SimpleRect
*resize_rect
= new SimpleRect(
2316 *_note_group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2318 // calculate the colors: get the color settings
2319 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2320 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2323 // make the resize preview notes more transparent and bright
2324 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2326 // calculate color based on note velocity
2327 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2328 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2332 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2333 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2335 resize_data
->resize_rect
= resize_rect
;
2336 _resize_data
.push_back(resize_data
);
2341 /** Update resizing notes while user drags.
2342 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2343 * @param at_front which end of the note (true == note on, false == note off)
2344 * @param delta_x change in mouse position since the start of the drag
2345 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2346 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2347 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2348 * as the \a primary note.
2351 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2353 bool cursor_set
= false;
2355 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2356 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2357 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2362 current_x
= canvas_note
->x1() + delta_x
;
2364 current_x
= primary
->x1() + delta_x
;
2368 current_x
= canvas_note
->x2() + delta_x
;
2370 current_x
= primary
->x2() + delta_x
;
2375 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2376 resize_rect
->property_x2() = canvas_note
->x2();
2378 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2379 resize_rect
->property_x1() = canvas_note
->x1();
2385 beats
= snap_pixel_to_frame (current_x
);
2386 beats
= frames_to_beats (beats
);
2391 if (beats
< canvas_note
->note()->end_time()) {
2392 len
= canvas_note
->note()->time() - beats
;
2393 len
+= canvas_note
->note()->length();
2398 if (beats
>= canvas_note
->note()->time()) {
2399 len
= beats
- canvas_note
->note()->time();
2406 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2407 show_verbose_cursor (buf
, 0, 0);
2416 /** Finish resizing notes when the user releases the mouse button.
2417 * Parameters the same as for \a update_resizing().
2420 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2422 start_note_diff_command (_("resize notes"));
2424 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2425 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2426 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2431 current_x
= canvas_note
->x1() + delta_x
;
2433 current_x
= primary
->x1() + delta_x
;
2437 current_x
= canvas_note
->x2() + delta_x
;
2439 current_x
= primary
->x2() + delta_x
;
2443 current_x
= snap_pixel_to_frame (current_x
);
2444 current_x
= frames_to_beats (current_x
);
2446 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2447 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::StartTime
, current_x
);
2449 double len
= canvas_note
->note()->time() - current_x
;
2450 len
+= canvas_note
->note()->length();
2453 /* XXX convert to beats */
2454 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2459 double len
= current_x
- canvas_note
->note()->time();
2462 /* XXX convert to beats */
2463 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2471 _resize_data
.clear();
2476 MidiRegionView::change_note_channel (CanvasNoteEvent
* event
, int8_t channel
)
2478 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Channel
, (uint8_t) channel
);
2482 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2484 uint8_t new_velocity
;
2487 new_velocity
= event
->note()->velocity() + velocity
;
2488 clamp_to_0_127(new_velocity
);
2490 new_velocity
= velocity
;
2493 event
->set_selected (event
->selected()); // change color
2495 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Velocity
, new_velocity
);
2499 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2504 new_note
= event
->note()->note() + note
;
2509 clamp_to_0_127 (new_note
);
2510 note_diff_add_change (event
, MidiModel::NoteDiffCommand::NoteNumber
, new_note
);
2514 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2516 bool change_start
= false;
2517 bool change_length
= false;
2518 Evoral::MusicalTime new_start
= 0;
2519 Evoral::MusicalTime new_length
= 0;
2521 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2523 front_delta: if positive - move the start of the note later in time (shortening it)
2524 if negative - move the start of the note earlier in time (lengthening it)
2526 end_delta: if positive - move the end of the note later in time (lengthening it)
2527 if negative - move the end of the note earlier in time (shortening it)
2531 if (front_delta
< 0) {
2533 if (event
->note()->time() < -front_delta
) {
2536 new_start
= event
->note()->time() + front_delta
; // moves earlier
2539 /* start moved toward zero, so move the end point out to where it used to be.
2540 Note that front_delta is negative, so this increases the length.
2543 new_length
= event
->note()->length() - front_delta
;
2544 change_start
= true;
2545 change_length
= true;
2549 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2551 if (new_pos
< event
->note()->end_time()) {
2552 new_start
= event
->note()->time() + front_delta
;
2553 /* start moved toward the end, so move the end point back to where it used to be */
2554 new_length
= event
->note()->length() - front_delta
;
2555 change_start
= true;
2556 change_length
= true;
2563 bool can_change
= true;
2564 if (end_delta
< 0) {
2565 if (event
->note()->length() < -end_delta
) {
2571 new_length
= event
->note()->length() + end_delta
;
2572 change_length
= true;
2577 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_start
);
2580 if (change_length
) {
2581 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, new_length
);
2586 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2588 Evoral::MusicalTime new_time
;
2592 if (event
->note()->time() < -delta
) {
2595 new_time
= event
->note()->time() + delta
;
2598 new_time
= event
->note()->time() + delta
;
2604 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2608 MidiRegionView::change_note_length (CanvasNoteEvent
* event
, Evoral::MusicalTime t
)
2610 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, t
);
2614 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2618 if (_selection
.empty()) {
2633 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2634 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2640 start_note_diff_command (_("change velocities"));
2642 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2643 Selection::iterator next
= i
;
2645 change_note_velocity (*i
, delta
, true);
2651 if (!_selection
.empty()) {
2653 snprintf (buf
, sizeof (buf
), "Vel %d",
2654 (int) (*_selection
.begin())->note()->velocity());
2655 show_verbose_cursor (buf
, 10, 10);
2661 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2663 if (_selection
.empty()) {
2680 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2682 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2686 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2693 start_note_diff_command (_("transpose"));
2695 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2696 Selection::iterator next
= i
;
2698 change_note_note (*i
, delta
, true);
2706 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, Evoral::MusicalTime delta
, bool start
, bool end
)
2712 /* grab the current grid distance */
2714 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2716 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2717 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2727 start_note_diff_command (_("change note lengths"));
2729 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2730 Selection::iterator next
= i
;
2733 /* note the negation of the delta for start */
2735 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2744 MidiRegionView::nudge_notes (bool forward
)
2746 if (_selection
.empty()) {
2750 /* pick a note as the point along the timeline to get the nudge distance.
2751 its not necessarily the earliest note, so we may want to pull the notes out
2752 into a vector and sort before using the first one.
2755 framepos_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2757 framepos_t distance
;
2759 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2761 /* grid is off - use nudge distance */
2763 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2769 framepos_t next_pos
= ref_point
;
2772 if (max_framepos
- 1 < next_pos
) {
2776 if (next_pos
== 0) {
2782 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2783 distance
= ref_point
- next_pos
;
2786 if (distance
== 0) {
2790 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2796 start_note_diff_command (_("nudge"));
2798 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2799 Selection::iterator next
= i
;
2801 change_note_time (*i
, delta
, true);
2809 MidiRegionView::change_channel(uint8_t channel
)
2811 start_note_diff_command(_("change channel"));
2812 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2813 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::Channel
, channel
);
2821 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2823 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2825 _pre_enter_cursor
= editor
->get_canvas_cursor ();
2827 if (_mouse_state
== SelectTouchDragging
) {
2828 note_selected (ev
, true);
2831 show_verbose_cursor (ev
->note ());
2835 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2837 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2839 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2840 (*i
)->hide_velocity ();
2843 editor
->verbose_cursor()->hide ();
2845 if (_pre_enter_cursor
) {
2846 editor
->set_canvas_cursor (_pre_enter_cursor
);
2847 _pre_enter_cursor
= 0;
2852 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange
* ev
)
2855 s
<< ((int) ev
->patch()->program() + 1) << ":" << (ev
->patch()->bank() + 1);
2856 show_verbose_cursor (s
.str(), 10, 20);
2860 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange
*)
2862 trackview
.editor().verbose_cursor()->hide ();
2866 MidiRegionView::note_mouse_position (float x_fraction
, float /*y_fraction*/, bool can_set_cursor
)
2868 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2870 if (x_fraction
> 0.0 && x_fraction
< 0.25) {
2871 editor
->set_canvas_cursor (editor
->cursors()->left_side_trim
);
2872 } else if (x_fraction
>= 0.75 && x_fraction
< 1.0) {
2873 editor
->set_canvas_cursor (editor
->cursors()->right_side_trim
);
2875 if (_pre_enter_cursor
&& can_set_cursor
) {
2876 editor
->set_canvas_cursor (_pre_enter_cursor
);
2882 MidiRegionView::set_frame_color()
2886 TimeAxisViewItem::set_frame_color ();
2893 f
= ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2894 } else if (high_enough_for_name
) {
2895 f
= ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2900 if (!rect_visible
) {
2901 f
= UINT_RGBA_CHANGE_A (f
, 0);
2904 frame
->property_fill_color_rgba() = f
;
2908 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2912 case FilterChannels
:
2913 _force_channel
= -1;
2916 _force_channel
= mask
;
2917 mask
= 0xFFFF; // Show all notes as active (below)
2920 // Update notes for selection
2921 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2922 (*i
)->on_channel_selection_change(mask
);
2925 _last_channel_selection
= mask
;
2927 _patch_changes
.clear ();
2928 display_patch_changes ();
2932 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2934 _model_name
= model
;
2935 _custom_device_mode
= custom_device_mode
;
2940 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
2942 if (_selection
.empty()) {
2946 PublicEditor
& editor (trackview
.editor());
2951 editor
.get_cut_buffer().add (selection_as_cut_buffer());
2959 start_note_diff_command();
2961 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2967 note_diff_remove_note (*i
);
2977 MidiRegionView::selection_as_cut_buffer () const
2981 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2982 NoteType
* n
= (*i
)->note().get();
2983 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
2986 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
2992 /** This method handles undo */
2994 MidiRegionView::paste (framepos_t pos
, float times
, const MidiCutBuffer
& mcb
)
3000 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("MIDI paste @ %1 times %2\n", pos
, times
));
3002 trackview
.session()->begin_reversible_command (_("paste"));
3004 start_note_diff_command (_("paste"));
3006 Evoral::MusicalTime beat_delta
;
3007 Evoral::MusicalTime paste_pos_beats
;
3008 Evoral::MusicalTime duration
;
3009 Evoral::MusicalTime end_point
= 0;
3011 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
3012 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
3013 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
3014 paste_pos_beats
= 0;
3016 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",
3017 (*mcb
.notes().begin())->time(),
3018 (*mcb
.notes().rbegin())->end_time(),
3019 duration
, pos
, _region
->position(),
3020 paste_pos_beats
, beat_delta
));
3024 for (int n
= 0; n
< (int) times
; ++n
) {
3026 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
3028 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
3029 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
3031 /* make all newly added notes selected */
3033 note_diff_add_note (copied_note
, true);
3034 end_point
= copied_note
->end_time();
3037 paste_pos_beats
+= duration
;
3040 /* if we pasted past the current end of the region, extend the region */
3042 framepos_t end_frame
= _region
->position() + beats_to_frames (end_point
);
3043 framepos_t region_end
= _region
->position() + _region
->length() - 1;
3045 if (end_frame
> region_end
) {
3047 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste extended region from %1 to %2\n", region_end
, end_frame
));
3049 _region
->clear_changes ();
3050 _region
->set_length (end_frame
, this);
3051 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
3056 trackview
.session()->commit_reversible_command ();
3059 struct EventNoteTimeEarlyFirstComparator
{
3060 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
3061 return a
->note()->time() < b
->note()->time();
3066 MidiRegionView::time_sort_events ()
3068 if (!_sort_needed
) {
3072 EventNoteTimeEarlyFirstComparator cmp
;
3075 _sort_needed
= false;
3079 MidiRegionView::goto_next_note ()
3081 // framepos_t pos = -1;
3082 bool use_next
= false;
3084 if (_events
.back()->selected()) {
3088 time_sort_events ();
3090 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3091 if ((*i
)->selected()) {
3094 } else if (use_next
) {
3096 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3101 /* use the first one */
3103 unique_select (_events
.front());
3108 MidiRegionView::goto_previous_note ()
3110 // framepos_t pos = -1;
3111 bool use_next
= false;
3113 if (_events
.front()->selected()) {
3117 time_sort_events ();
3119 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
3120 if ((*i
)->selected()) {
3123 } else if (use_next
) {
3125 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3130 /* use the last one */
3132 unique_select (*(_events
.rbegin()));
3136 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
3138 bool had_selected
= false;
3140 time_sort_events ();
3142 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3143 if ((*i
)->selected()) {
3144 selected
.insert ((*i
)->note());
3145 had_selected
= true;
3149 if (allow_all_if_none_selected
&& !had_selected
) {
3150 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3151 selected
.insert ((*i
)->note());
3157 MidiRegionView::update_ghost_note (double x
, double y
)
3159 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3164 _note_group
->w2i (x
, y
);
3165 framepos_t
const f
= snap_pixel_to_frame (x
);
3168 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
3174 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
3176 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
3177 _ghost_note
->note()->set_length (length
);
3178 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
3179 _ghost_note
->note()->set_channel (mtv
->get_channel_for_add ());
3181 /* the ghost note does not appear in ghost regions, so pass false in here */
3182 update_note (_ghost_note
, false);
3184 show_verbose_cursor (_ghost_note
->note ());
3188 MidiRegionView::create_ghost_note (double x
, double y
)
3193 boost::shared_ptr
<NoteType
> g (new NoteType
);
3194 _ghost_note
= new NoEventCanvasNote (*this, *_note_group
, g
);
3195 _ghost_note
->property_outline_color_rgba() = 0x000000aa;
3196 update_ghost_note (x
, y
);
3197 _ghost_note
->show ();
3202 show_verbose_cursor (_ghost_note
->note ());
3206 MidiRegionView::snap_changed ()
3212 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
3216 MidiRegionView::drop_down_keys ()
3218 _mouse_state
= None
;
3222 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double /*x*/, double y
)
3224 double note
= midi_stream_view()->y_to_note(y
);
3226 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3228 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
3230 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
3231 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3232 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
3233 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3238 bool add_mrv_selection
= false;
3240 if (_selection
.empty()) {
3241 add_mrv_selection
= true;
3244 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
3245 if (_selection
.insert (*i
).second
) {
3246 (*i
)->set_selected (true);
3250 if (add_mrv_selection
) {
3251 PublicEditor
& editor (trackview
.editor());
3252 editor
.get_selection().add (this);
3257 MidiRegionView::color_handler ()
3259 RegionView::color_handler ();
3261 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3262 (*i
)->set_selected ((*i
)->selected()); // will change color
3265 /* XXX probably more to do here */
3269 MidiRegionView::enable_display (bool yn
)
3271 RegionView::enable_display (yn
);
3278 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos
)
3280 if (_step_edit_cursor
== 0) {
3281 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
3283 _step_edit_cursor
= new ArdourCanvas::SimpleRect (*group
);
3284 _step_edit_cursor
->property_y1() = 0;
3285 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
3286 _step_edit_cursor
->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3287 _step_edit_cursor
->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3290 move_step_edit_cursor (pos
);
3291 _step_edit_cursor
->show ();
3295 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos
)
3297 _step_edit_cursor_position
= pos
;
3299 if (_step_edit_cursor
) {
3300 double pixel
= trackview
.editor().frame_to_pixel (beats_to_frames (pos
));
3301 _step_edit_cursor
->property_x1() = pixel
;
3302 set_step_edit_cursor_width (_step_edit_cursor_width
);
3307 MidiRegionView::hide_step_edit_cursor ()
3309 if (_step_edit_cursor
) {
3310 _step_edit_cursor
->hide ();
3315 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats
)
3317 _step_edit_cursor_width
= beats
;
3319 if (_step_edit_cursor
) {
3320 _step_edit_cursor
->property_x2() = _step_edit_cursor
->property_x1() + trackview
.editor().frame_to_pixel (beats_to_frames (beats
));
3324 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3325 * @param buf Data that has been recorded.
3326 * @param w Source that this data will end up in.
3329 MidiRegionView::data_recorded (boost::shared_ptr
<MidiBuffer
> buf
, boost::weak_ptr
<MidiSource
> w
)
3331 if (!_active_notes
) {
3332 /* we aren't actively being recorded to */
3336 boost::shared_ptr
<MidiSource
> src
= w
.lock ();
3337 if (!src
|| src
!= midi_region()->midi_source()) {
3338 /* recorded data was not destined for our source */
3342 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*> (&trackview
);
3343 BeatsFramesConverter
converter (trackview
.session()->tempo_map(), mtv
->midi_track()->get_capture_start_frame (0));
3345 framepos_t back
= max_framepos
;
3347 for (MidiBuffer::iterator i
= buf
->begin(); i
!= buf
->end(); ++i
) {
3348 Evoral::MIDIEvent
<MidiBuffer::TimeType
> const ev (*i
, false);
3349 assert (ev
.buffer ());
3351 Evoral::MusicalTime
const time_beats
= converter
.from (ev
.time () - converter
.origin_b ());
3353 if (ev
.type() == MIDI_CMD_NOTE_ON
) {
3355 boost::shared_ptr
<NoteType
> note (
3356 new NoteType (ev
.channel(), time_beats
, 0, ev
.note(), ev
.velocity())
3359 add_note (note
, true);
3361 /* fix up our note range */
3362 if (ev
.note() < _current_range_min
) {
3363 midi_stream_view()->apply_note_range (ev
.note(), _current_range_max
, true);
3364 } else if (ev
.note() > _current_range_max
) {
3365 midi_stream_view()->apply_note_range (_current_range_min
, ev
.note(), true);
3368 } else if (ev
.type() == MIDI_CMD_NOTE_OFF
) {
3369 resolve_note (ev
.note (), time_beats
);
3375 midi_stream_view()->check_record_layers (region(), back
);
3379 MidiRegionView::trim_front_starting ()
3381 /* Reparent the note group to the region view's parent, so that it doesn't change
3382 when the region view is trimmed.
3384 _temporary_note_group
= new ArdourCanvas::Group (*group
->property_parent ());
3385 _temporary_note_group
->move (group
->property_x(), group
->property_y());
3386 _note_group
->reparent (*_temporary_note_group
);
3390 MidiRegionView::trim_front_ending ()
3392 _note_group
->reparent (*group
);
3393 delete _temporary_note_group
;
3394 _temporary_note_group
= 0;
3396 if (_region
->start() < 0) {
3397 /* Trim drag made start time -ve; fix this */
3398 midi_region()->fix_negative_start ();
3403 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange
* pc
)
3405 PatchChangeDialog
d (&_time_converter
, trackview
.session(), *pc
->patch (), Gtk::Stock::APPLY
);
3406 if (d
.run () != Gtk::RESPONSE_ACCEPT
) {
3410 change_patch_change (pc
->patch(), d
.patch ());
3415 MidiRegionView::show_verbose_cursor (boost::shared_ptr
<NoteType
> n
) const
3418 snprintf (buf
, sizeof (buf
), "%s (%d) Chn %d\nVel %d",
3419 Evoral::midi_note_name (n
->note()).c_str(),
3421 (int) n
->channel() + 1,
3422 (int) n
->velocity());
3424 show_verbose_cursor (buf
, 10, 20);
3428 MidiRegionView::show_verbose_cursor (string
const & text
, double xoffset
, double yoffset
) const
3432 trackview
.editor().get_pointer_position (wx
, wy
);
3437 trackview
.editor().verbose_cursor()->set (text
, wx
, wy
);
3438 trackview
.editor().verbose_cursor()->show ();