2 Copyright (C) 2001-2007 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 (sigc::mem_fun (this, &MidiRegionView::canvas_event
), false);
260 midi_view()->signal_channel_mode_changed().connect(
261 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed
));
263 midi_view()->signal_midi_patch_settings_changed().connect(
264 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed
));
266 trackview
.editor().SnapChanged
.connect (snap_changed_connection
, invalidator (*this), ui_bind (&MidiRegionView::snap_changed
, this), gui_context ());
268 connect_to_diskstream ();
272 MidiRegionView::connect_to_diskstream ()
274 midi_view()->midi_track()->DataRecorded
.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::data_recorded
, this, _1
, _2
), gui_context ());
278 MidiRegionView::canvas_event(GdkEvent
* ev
)
281 case GDK_ENTER_NOTIFY
:
282 case GDK_LEAVE_NOTIFY
:
283 _last_event_x
= ev
->crossing
.x
;
284 _last_event_y
= ev
->crossing
.y
;
286 case GDK_MOTION_NOTIFY
:
287 _last_event_x
= ev
->motion
.x
;
288 _last_event_y
= ev
->motion
.y
;
294 if (!trackview
.editor().internal_editing()) {
300 return scroll (&ev
->scroll
);
303 return key_press (&ev
->key
);
305 case GDK_KEY_RELEASE
:
306 return key_release (&ev
->key
);
308 case GDK_BUTTON_PRESS
:
309 return button_press (&ev
->button
);
311 case GDK_2BUTTON_PRESS
:
314 case GDK_BUTTON_RELEASE
:
315 return button_release (&ev
->button
);
317 case GDK_ENTER_NOTIFY
:
318 return enter_notify (&ev
->crossing
);
320 case GDK_LEAVE_NOTIFY
:
321 return leave_notify (&ev
->crossing
);
323 case GDK_MOTION_NOTIFY
:
324 return motion (&ev
->motion
);
334 MidiRegionView::remove_ghost_note ()
341 MidiRegionView::enter_notify (GdkEventCrossing
* ev
)
343 trackview
.editor().MouseModeChanged
.connect (
344 _mouse_mode_connection
, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed
, this), gui_context ()
347 Keyboard::magic_widget_grab_focus();
350 if (trackview
.editor().current_mouse_mode() == MouseRange
) {
351 create_ghost_note (ev
->x
, ev
->y
);
358 MidiRegionView::leave_notify (GdkEventCrossing
*)
360 _mouse_mode_connection
.disconnect ();
362 trackview
.editor().verbose_cursor()->hide ();
363 remove_ghost_note ();
368 MidiRegionView::mouse_mode_changed ()
370 if (trackview
.editor().current_mouse_mode() == MouseRange
&& trackview
.editor().internal_editing()) {
371 create_ghost_note (_last_event_x
, _last_event_y
);
373 remove_ghost_note ();
374 trackview
.editor().verbose_cursor()->hide ();
379 MidiRegionView::button_press (GdkEventButton
* ev
)
381 if (ev
->button
!= 1) {
388 group
->w2i (_last_x
, _last_y
);
390 if (_mouse_state
!= SelectTouchDragging
) {
392 _pressed_button
= ev
->button
;
393 _mouse_state
= Pressed
;
398 _pressed_button
= ev
->button
;
404 MidiRegionView::button_release (GdkEventButton
* ev
)
406 double event_x
, event_y
;
407 framepos_t event_frame
= 0;
409 if (ev
->button
!= 1) {
416 group
->w2i(event_x
, event_y
);
417 group
->ungrab(ev
->time
);
419 event_frame
= trackview
.editor().pixel_to_frame(event_x
);
421 switch (_mouse_state
) {
422 case Pressed
: // Clicked
424 switch (trackview
.editor().current_mouse_mode()) {
430 if (Keyboard::is_insert_note_event(ev
)){
432 double event_x
, event_y
;
436 group
->w2i(event_x
, event_y
);
439 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
445 create_note_at (event_x
, event_y
, beats
, true);
453 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
459 create_note_at (event_x
, event_y
, beats
, true);
470 case SelectRectDragging
: // Select drag done
477 case AddDragging
: // Add drag done
481 if (Keyboard::is_insert_note_event(ev
) || trackview
.editor().current_mouse_mode() == MouseRange
){
483 if (_drag_rect
->property_x2() > _drag_rect
->property_x1() + 2) {
485 const double x
= _drag_rect
->property_x1();
486 const double length
= trackview
.editor().pixel_to_frame (_drag_rect
->property_x2() - _drag_rect
->property_x1());
488 create_note_at (x
, _drag_rect
->property_y1(), frames_to_beats(length
), true);
495 create_ghost_note (ev
->x
, ev
->y
);
505 MidiRegionView::motion (GdkEventMotion
* ev
)
507 double event_x
, event_y
;
508 framepos_t event_frame
= 0;
512 group
->w2i(event_x
, event_y
);
514 // convert event_x to global frame
515 event_frame
= snap_pixel_to_frame (event_x
);
517 if (!_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
518 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())
519 && _mouse_state
!= AddDragging
){
521 create_ghost_note (ev
->x
, ev
->y
);
523 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
524 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())){
526 update_ghost_note (ev
->x
, ev
->y
);
528 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
){
533 trackview
.editor().verbose_cursor()->hide ();
535 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() == MouseRange
) {
536 update_ghost_note (ev
->x
, ev
->y
);
539 /* any motion immediately hides velocity text that may have been visible */
541 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
542 (*i
)->hide_velocity ();
545 switch (_mouse_state
) {
546 case Pressed
: // Maybe start a drag, if we've moved a bit
548 if (fabs (event_x
- _last_x
) < 1 && fabs (event_y
- _last_y
) < 1) {
549 /* no appreciable movement since the button was pressed */
554 if (_pressed_button
== 1 && trackview
.editor().current_mouse_mode() == MouseObject
555 && !Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())) {
557 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
558 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
562 _drag_start_x
= event_x
;
563 _drag_start_y
= event_y
;
565 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
566 _drag_rect
->property_x1() = event_x
;
567 _drag_rect
->property_y1() = event_y
;
568 _drag_rect
->property_x2() = event_x
;
569 _drag_rect
->property_y2() = event_y
;
570 _drag_rect
->property_outline_what() = 0xFF;
571 _drag_rect
->property_outline_color_rgba()
572 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
573 _drag_rect
->property_fill_color_rgba()
574 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
576 _mouse_state
= SelectRectDragging
;
579 // Add note drag start
580 } else if (trackview
.editor().internal_editing()) {
585 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
586 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
590 _drag_start_x
= event_x
;
591 _drag_start_y
= event_y
;
593 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
594 _drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
596 _drag_rect
->property_y1() = midi_stream_view()->note_to_y(
597 midi_stream_view()->y_to_note(event_y
));
598 _drag_rect
->property_x2() = trackview
.editor().frame_to_pixel(event_frame
);
599 _drag_rect
->property_y2() = _drag_rect
->property_y1()
600 + floor(midi_stream_view()->note_height());
601 _drag_rect
->property_outline_what() = 0xFF;
602 _drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
603 _drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
605 _mouse_state
= AddDragging
;
612 trackview
.editor().verbose_cursor()->hide ();
620 case SelectRectDragging
: // Select drag motion
621 case AddDragging
: // Add note drag motion
626 GdkModifierType state
;
627 gdk_window_get_pointer(ev
->window
, &t_x
, &t_y
, &state
);
632 if (_mouse_state
== AddDragging
){
633 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
638 if (event_x
> _drag_start_x
){
639 _drag_rect
->property_x2() = event_x
;
642 _drag_rect
->property_x1() = event_x
;
646 if (_drag_rect
&& _mouse_state
== SelectRectDragging
) {
648 if (event_y
> _drag_start_y
){
649 _drag_rect
->property_y2() = event_y
;
652 _drag_rect
->property_y1() = event_y
;
655 update_drag_selection(_drag_start_x
, event_x
, _drag_start_y
, event_y
);
661 case SelectTouchDragging
:
673 MidiRegionView::scroll (GdkEventScroll
* ev
)
675 if (_selection
.empty()) {
679 trackview
.editor().verbose_cursor()->hide ();
681 bool fine
= !Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
);
683 if (ev
->direction
== GDK_SCROLL_UP
) {
684 change_velocities (true, fine
, false);
685 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
686 change_velocities (false, fine
, false);
692 MidiRegionView::key_press (GdkEventKey
* ev
)
694 /* since GTK bindings are generally activated on press, and since
695 detectable auto-repeat is the name of the game and only sends
696 repeated presses, carry out key actions at key press, not release.
699 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
){
700 _mouse_state
= SelectTouchDragging
;
703 } else if (ev
->keyval
== GDK_Escape
) {
707 } else if (ev
->keyval
== GDK_comma
|| ev
->keyval
== GDK_period
) {
709 bool start
= (ev
->keyval
== GDK_comma
);
710 bool end
= (ev
->keyval
== GDK_period
);
711 bool shorter
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
);
712 bool fine
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
714 change_note_lengths (fine
, shorter
, 0.0, start
, end
);
718 } else if (ev
->keyval
== GDK_Delete
) {
723 } else if (ev
->keyval
== GDK_Tab
) {
725 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
726 goto_previous_note ();
732 } else if (ev
->keyval
== GDK_Up
) {
734 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
735 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
737 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
738 change_velocities (true, fine
, allow_smush
);
740 transpose (true, fine
, allow_smush
);
744 } else if (ev
->keyval
== GDK_Down
) {
746 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
747 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
749 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
750 change_velocities (false, fine
, allow_smush
);
752 transpose (false, fine
, allow_smush
);
756 } else if (ev
->keyval
== GDK_Left
) {
761 } else if (ev
->keyval
== GDK_Right
) {
766 } else if (ev
->keyval
== GDK_Control_L
) {
775 MidiRegionView::key_release (GdkEventKey
* ev
)
777 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
785 MidiRegionView::show_list_editor ()
788 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
790 _list_editor
->present ();
793 /** Add a note to the model, and the view, at a canvas (click) coordinate.
794 * \param x horizontal position in pixels
795 * \param y vertical position in pixels
796 * \param length duration of the note in beats, which will be snapped to the grid
797 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
800 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
802 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
803 MidiStreamView
* const view
= mtv
->midi_view();
805 double note
= view
->y_to_note(y
);
808 assert(note
<= 127.0);
810 // Start of note in frames relative to region start
811 framepos_t
const start_frames
= snap_pixel_to_frame (x
);
812 assert(start_frames
>= 0);
815 length
= frames_to_beats(
816 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
818 assert (length
!= 0);
821 length
= frames_to_beats (beats_to_frames (length
) - 1);
824 const boost::shared_ptr
<NoteType
> new_note (new NoteType (get_channel_for_add (),
825 frames_to_beats(start_frames
+ _region
->start()), length
,
826 (uint8_t)note
, 0x40));
828 if (_model
->contains (new_note
)) {
832 view
->update_note_range(new_note
->note());
834 MidiModel::NoteDiffCommand
* cmd
= _model
->new_note_diff_command("add note");
836 _model
->apply_command(*trackview
.session(), cmd
);
838 play_midi_note (new_note
);
842 MidiRegionView::clear_events()
847 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
848 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
853 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
858 _patch_changes
.clear();
860 _optimization_iterator
= _events
.end();
864 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
868 content_connection
.disconnect ();
869 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
873 if (_enable_display
) {
879 MidiRegionView::start_note_diff_command (string name
)
881 if (!_note_diff_command
) {
882 _note_diff_command
= _model
->new_note_diff_command (name
);
887 MidiRegionView::note_diff_add_note (const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
889 if (_note_diff_command
) {
890 _note_diff_command
->add (note
);
893 _marked_for_selection
.insert(note
);
896 _marked_for_velocity
.insert(note
);
901 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent
* ev
)
903 if (_note_diff_command
&& ev
->note()) {
904 _note_diff_command
->remove(ev
->note());
909 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
910 MidiModel::NoteDiffCommand::Property property
,
913 if (_note_diff_command
) {
914 _note_diff_command
->change (ev
->note(), property
, val
);
919 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
920 MidiModel::NoteDiffCommand::Property property
,
921 Evoral::MusicalTime val
)
923 if (_note_diff_command
) {
924 _note_diff_command
->change (ev
->note(), property
, val
);
929 MidiRegionView::apply_diff (bool as_subcommand
)
933 if (!_note_diff_command
) {
937 if ((add_or_remove
= _note_diff_command
->adds_or_removes())) {
938 // Mark all selected notes for selection when model reloads
939 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
940 _marked_for_selection
.insert((*i
)->note());
945 _model
->apply_command_as_subcommand (*trackview
.session(), _note_diff_command
);
947 _model
->apply_command (*trackview
.session(), _note_diff_command
);
950 _note_diff_command
= 0;
951 midi_view()->midi_track()->playlist_modified();
954 _marked_for_selection
.clear();
957 _marked_for_velocity
.clear();
961 MidiRegionView::abort_command()
963 delete _note_diff_command
;
964 _note_diff_command
= 0;
969 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
971 if (_optimization_iterator
!= _events
.end()) {
972 ++_optimization_iterator
;
975 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
976 return *_optimization_iterator
;
979 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
980 if ((*_optimization_iterator
)->note() == note
) {
981 return *_optimization_iterator
;
989 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
991 MidiModel::Notes notes
;
992 _model
->get_notes (notes
, op
, val
, chan_mask
);
994 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
995 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
1003 MidiRegionView::redisplay_model()
1005 // Don't redisplay the model if we're currently recording and displaying that
1006 if (_active_notes
) {
1014 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1015 (*i
)->invalidate ();
1018 MidiModel::ReadLock
lock(_model
->read_lock());
1020 MidiModel::Notes
& notes (_model
->notes());
1021 _optimization_iterator
= _events
.begin();
1023 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1025 boost::shared_ptr
<NoteType
> note (*n
);
1026 CanvasNoteEvent
* cne
;
1029 if (note_in_region_range (note
, visible
)) {
1031 if ((cne
= find_canvas_note (note
)) != 0) {
1038 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
1040 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
1052 add_note (note
, visible
);
1057 if ((cne
= find_canvas_note (note
)) != 0) {
1065 /* remove note items that are no longer valid */
1067 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
1068 if (!(*i
)->valid ()) {
1070 i
= _events
.erase (i
);
1076 _patch_changes
.clear();
1080 display_patch_changes ();
1082 _marked_for_selection
.clear ();
1083 _marked_for_velocity
.clear ();
1085 /* we may have caused _events to contain things out of order (e.g. if a note
1086 moved earlier or later). we don't generally need them in time order, but
1087 make a note that a sort is required for those cases that require it.
1090 _sort_needed
= true;
1094 MidiRegionView::display_patch_changes ()
1096 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1097 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
1099 for (uint8_t i
= 0; i
< 16; ++i
) {
1100 if (chn_mask
& (1<<i
)) {
1101 display_patch_changes_on_channel (i
);
1107 MidiRegionView::display_patch_changes_on_channel (uint8_t channel
)
1109 for (MidiModel::PatchChanges::const_iterator i
= _model
->patch_changes().begin(); i
!= _model
->patch_changes().end(); ++i
) {
1111 if ((*i
)->channel() != channel
) {
1115 MIDI::Name::PatchPrimaryKey
patch_key ((*i
)->bank_msb (), (*i
)->bank_lsb (), (*i
)->program ());
1117 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1118 MIDI::Name::MidiPatchManager::instance().find_patch(
1119 _model_name
, _custom_device_mode
, channel
, patch_key
);
1122 add_canvas_patch_change (*i
, patch
->name());
1125 /* program and bank numbers are zero-based: convert to one-based */
1126 snprintf (buf
, 16, "%d\n%d", (*i
)->program() + 1, (*i
)->bank() + 1);
1127 add_canvas_patch_change (*i
, buf
);
1133 MidiRegionView::display_sysexes()
1135 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1136 Evoral::MusicalTime time
= (*i
)->time();
1141 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1142 str
<< int((*i
)->buffer()[b
]);
1143 if (b
!= (*i
)->size() -1) {
1147 string text
= str
.str();
1149 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1151 double height
= midi_stream_view()->contents_height();
1153 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1154 new CanvasSysEx(*this, *_note_group
, text
, height
, x
, 1.0));
1156 // Show unless patch change is beyond the region bounds
1157 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1163 _sys_exes
.push_back(sysex
);
1168 MidiRegionView::~MidiRegionView ()
1170 in_destructor
= true;
1172 trackview
.editor().verbose_cursor()->hide ();
1174 note_delete_connection
.disconnect ();
1176 delete _list_editor
;
1178 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1180 if (_active_notes
) {
1188 delete _note_diff_command
;
1189 delete _step_edit_cursor
;
1190 delete _temporary_note_group
;
1194 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1196 RegionView::region_resized(what_changed
);
1198 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1199 set_duration(_region
->length(), 0);
1200 if (_enable_display
) {
1207 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1209 RegionView::reset_width_dependent_items(pixel_width
);
1210 assert(_pixel_width
== pixel_width
);
1212 if (_enable_display
) {
1216 move_step_edit_cursor (_step_edit_cursor_position
);
1217 set_step_edit_cursor_width (_step_edit_cursor_width
);
1221 MidiRegionView::set_height (double height
)
1223 static const double FUDGE
= 2.0;
1224 const double old_height
= _height
;
1225 RegionView::set_height(height
);
1226 _height
= height
- FUDGE
;
1228 apply_note_range(midi_stream_view()->lowest_note(),
1229 midi_stream_view()->highest_note(),
1230 height
!= old_height
+ FUDGE
);
1233 name_pixbuf
->raise_to_top();
1236 for (PatchChanges::iterator x
= _patch_changes
.begin(); x
!= _patch_changes
.end(); ++x
) {
1237 (*x
)->set_height (midi_stream_view()->contents_height());
1240 if (_step_edit_cursor
) {
1241 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
1246 /** Apply the current note range from the stream view
1247 * by repositioning/hiding notes as necessary
1250 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1252 if (!_enable_display
) {
1256 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1260 _current_range_min
= min
;
1261 _current_range_max
= max
;
1263 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1264 CanvasNoteEvent
* event
= *i
;
1265 boost::shared_ptr
<NoteType
> note (event
->note());
1267 if (note
->note() < _current_range_min
||
1268 note
->note() > _current_range_max
) {
1274 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1276 const double y1
= midi_stream_view()->note_to_y(note
->note());
1277 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1279 cnote
->property_y1() = y1
;
1280 cnote
->property_y2() = y2
;
1282 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1284 const double diamond_size
= update_hit (chit
);
1286 chit
->set_height (diamond_size
);
1292 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1296 double unit_position
= _region
->position () / samples_per_unit
;
1297 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1298 MidiGhostRegion
* ghost
;
1300 if (mtv
&& mtv
->midi_view()) {
1301 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1302 to allow having midi notes on top of note lines and waveforms.
1304 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1306 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1309 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1310 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1311 ghost
->add_note(note
);
1315 ghost
->set_height ();
1316 ghost
->set_duration (_region
->length() / samples_per_unit
);
1317 ghosts
.push_back (ghost
);
1319 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1325 /** Begin tracking note state for successive calls to add_event
1328 MidiRegionView::begin_write()
1330 assert(!_active_notes
);
1331 _active_notes
= new CanvasNote
*[128];
1332 for (unsigned i
=0; i
< 128; ++i
) {
1333 _active_notes
[i
] = 0;
1338 /** Destroy note state for add_event
1341 MidiRegionView::end_write()
1343 delete[] _active_notes
;
1345 _marked_for_selection
.clear();
1346 _marked_for_velocity
.clear();
1350 /** Resolve an active MIDI note (while recording).
1353 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1355 if (midi_view()->note_mode() != Sustained
) {
1359 if (_active_notes
&& _active_notes
[note
]) {
1361 const framepos_t end_time_frames
= beats_to_frames(end_time
);
1363 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1364 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1365 _active_notes
[note
] = 0;
1370 /** Extend active notes to rightmost edge of region (if length is changed)
1373 MidiRegionView::extend_active_notes()
1375 if (!_active_notes
) {
1379 for (unsigned i
=0; i
< 128; ++i
) {
1380 if (_active_notes
[i
]) {
1381 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1388 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1390 if (no_sound_notes
|| !trackview
.editor().sound_notes()) {
1394 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1396 if (!route_ui
|| !route_ui
->midi_track()) {
1400 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1406 MidiRegionView::play_midi_chord (vector
<boost::shared_ptr
<NoteType
> > notes
)
1408 if (no_sound_notes
|| !trackview
.editor().sound_notes()) {
1412 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1414 if (!route_ui
|| !route_ui
->midi_track()) {
1418 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1420 for (vector
<boost::shared_ptr
<NoteType
> >::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1429 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1431 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1433 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1434 (note_start_frames
< _region
->start());
1436 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1437 (note
->note() <= midi_stream_view()->highest_note());
1442 /** Update a canvas note's size from its model note.
1443 * @param ev Canvas note to update.
1444 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1447 MidiRegionView::update_note (CanvasNote
* ev
, bool update_ghost_regions
)
1449 boost::shared_ptr
<NoteType
> note
= ev
->note();
1451 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1453 /* trim note display to not overlap the end of its region */
1454 const framepos_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1456 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1457 const double y1
= midi_stream_view()->note_to_y(note
->note());
1458 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1460 ev
->property_x1() = x
;
1461 ev
->property_y1() = y1
;
1463 if (note
->length() > 0) {
1464 ev
->property_x2() = note_endpixel
;
1466 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1469 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1471 if (note
->length() == 0) {
1472 if (_active_notes
) {
1473 assert(note
->note() < 128);
1474 // If this note is already active there's a stuck note,
1475 // finish the old note rectangle
1476 if (_active_notes
[note
->note()]) {
1477 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1478 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1479 old_rect
->property_x2() = x
;
1480 old_rect
->property_outline_what() = (guint32
) 0xF;
1482 _active_notes
[note
->note()] = ev
;
1484 /* outline all but right edge */
1485 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1487 /* outline all edges */
1488 ev
->property_outline_what() = (guint32
) 0xF;
1491 if (update_ghost_regions
) {
1492 for (std::vector
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
1493 MidiGhostRegion
* gr
= dynamic_cast<MidiGhostRegion
*> (*i
);
1495 gr
->update_note (ev
);
1502 MidiRegionView::update_hit (CanvasHit
* ev
)
1504 boost::shared_ptr
<NoteType
> note
= ev
->note();
1506 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1507 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1508 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1509 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1513 return diamond_size
;
1516 /** Add a MIDI note to the view (with length).
1518 * If in sustained mode, notes with length 0 will be considered active
1519 * notes, and resolve_note should be called when the corresponding note off
1520 * event arrives, to properly display the note.
1523 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1525 CanvasNoteEvent
* event
= 0;
1527 assert(note
->time() >= 0);
1528 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1530 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1532 if (midi_view()->note_mode() == Sustained
) {
1534 CanvasNote
* ev_rect
= new CanvasNote(*this, *_note_group
, note
);
1536 update_note (ev_rect
);
1540 MidiGhostRegion
* gr
;
1542 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1543 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1544 gr
->add_note(ev_rect
);
1548 } else if (midi_view()->note_mode() == Percussive
) {
1550 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1552 CanvasHit
* ev_diamond
= new CanvasHit(*this, *_note_group
, diamond_size
, note
);
1554 update_hit (ev_diamond
);
1563 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1564 note_selected(event
, true);
1567 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1568 event
->show_velocity();
1571 event
->on_channel_selection_change(_last_channel_selection
);
1572 _events
.push_back(event
);
1581 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1582 MidiStreamView
* const view
= mtv
->midi_view();
1584 view
->update_note_range(note
->note());
1588 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1589 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1591 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1593 /* potentially extend region to hold new note */
1595 framepos_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1596 framepos_t region_end
= _region
->position() + _region
->length() - 1;
1598 if (end_frame
> region_end
) {
1599 _region
->set_length (end_frame
- _region
->position(), this);
1602 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1603 MidiStreamView
* const view
= mtv
->midi_view();
1605 view
->update_note_range(new_note
->note());
1607 _marked_for_selection
.clear ();
1610 start_note_diff_command (_("step add"));
1611 note_diff_add_note (new_note
, true, false);
1614 // last_step_edit_note = new_note;
1618 MidiRegionView::step_sustain (Evoral::MusicalTime beats
)
1620 change_note_lengths (false, false, beats
, false, true);
1624 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch
, const string
& displaytext
)
1626 assert (patch
->time() >= 0);
1628 const double x
= trackview
.editor().frame_to_pixel (beats_to_frames (patch
->time()));
1630 double const height
= midi_stream_view()->contents_height();
1632 boost::shared_ptr
<CanvasPatchChange
> patch_change
= boost::shared_ptr
<CanvasPatchChange
>(
1633 new CanvasPatchChange(*this, *_note_group
,
1638 _custom_device_mode
,
1642 // Show unless patch change is beyond the region bounds
1643 if (patch
->time() - _region
->start() >= _region
->length() || patch
->time() < _region
->start()) {
1644 patch_change
->hide();
1646 patch_change
->show();
1649 _patch_changes
.push_back (patch_change
);
1653 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1655 MidiModel::PatchChanges::iterator i
= _model
->patch_change_lower_bound (time
);
1656 while (i
!= _model
->patch_changes().end() && (*i
)->channel() != channel
) {
1660 if (i
!= _model
->patch_changes().end()) {
1661 key
.msb
= (*i
)->bank_msb ();
1662 key
.lsb
= (*i
)->bank_lsb ();
1663 key
.program_number
= (*i
)->program ();
1665 key
.msb
= key
.lsb
= key
.program_number
= 0;
1668 assert (key
.is_sane());
1673 MidiRegionView::change_patch_change (CanvasPatchChange
& pc
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1675 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1677 if (pc
.patch()->program() != new_patch
.program_number
) {
1678 c
->change_program (pc
.patch (), new_patch
.program_number
);
1681 int const new_bank
= (new_patch
.msb
<< 7) | new_patch
.lsb
;
1682 if (pc
.patch()->bank() != new_bank
) {
1683 c
->change_bank (pc
.patch (), new_bank
);
1686 _model
->apply_command (*trackview
.session(), c
);
1688 _patch_changes
.clear ();
1689 display_patch_changes ();
1693 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change
, const Evoral::PatchChange
<Evoral::MusicalTime
> & new_change
)
1695 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1697 if (old_change
->time() != new_change
.time()) {
1698 c
->change_time (old_change
, new_change
.time());
1701 if (old_change
->channel() != new_change
.channel()) {
1702 c
->change_channel (old_change
, new_change
.channel());
1705 if (old_change
->program() != new_change
.program()) {
1706 c
->change_program (old_change
, new_change
.program());
1709 if (old_change
->bank() != new_change
.bank()) {
1710 c
->change_bank (old_change
, new_change
.bank());
1713 _model
->apply_command (*trackview
.session(), c
);
1715 _patch_changes
.clear ();
1716 display_patch_changes ();
1719 /** Add a patch change to the region.
1720 * @param t Time in frames relative to region position
1721 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1722 * get_channel_for_add())
1725 MidiRegionView::add_patch_change (framecnt_t t
, Evoral::PatchChange
<Evoral::MusicalTime
> const & patch
)
1727 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("add patch change"));
1728 c
->add (MidiModel::PatchChangePtr (
1729 new Evoral::PatchChange
<Evoral::MusicalTime
> (
1730 frames_to_beats (t
+ midi_region()->start()), get_channel_for_add(), patch
.program(), patch
.bank()
1734 _model
->apply_command (*trackview
.session(), c
);
1736 _patch_changes
.clear ();
1737 display_patch_changes ();
1741 MidiRegionView::move_patch_change (CanvasPatchChange
& pc
, Evoral::MusicalTime t
)
1743 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("move patch change"));
1744 c
->change_time (pc
.patch (), t
);
1745 _model
->apply_command (*trackview
.session(), c
);
1747 _patch_changes
.clear ();
1748 display_patch_changes ();
1752 MidiRegionView::delete_patch_change (CanvasPatchChange
* pc
)
1754 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("delete patch change"));
1755 c
->remove (pc
->patch ());
1756 _model
->apply_command (*trackview
.session(), c
);
1758 _patch_changes
.clear ();
1759 display_patch_changes ();
1763 MidiRegionView::previous_patch (CanvasPatchChange
& patch
)
1765 if (patch
.patch()->program() < 127) {
1766 MIDI::Name::PatchPrimaryKey key
;
1767 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1768 key
.program_number
++;
1769 change_patch_change (patch
, key
);
1774 MidiRegionView::next_patch (CanvasPatchChange
& patch
)
1776 if (patch
.patch()->program() > 0) {
1777 MIDI::Name::PatchPrimaryKey key
;
1778 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1779 key
.program_number
--;
1780 change_patch_change (patch
, key
);
1785 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1787 if (_selection
.empty()) {
1791 if (_selection
.erase (cne
) > 0) {
1792 cerr
<< "Erased a CNE from selection\n";
1797 MidiRegionView::delete_selection()
1799 if (_selection
.empty()) {
1803 start_note_diff_command (_("delete selection"));
1805 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1806 if ((*i
)->selected()) {
1807 _note_diff_command
->remove((*i
)->note());
1817 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1819 start_note_diff_command (_("delete note"));
1820 _note_diff_command
->remove (n
);
1823 trackview
.editor().verbose_cursor()->hide ();
1827 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1829 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1830 if ((*i
)->selected() && (*i
) != ev
) {
1831 (*i
)->set_selected(false);
1832 (*i
)->hide_velocity();
1840 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1842 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1845 Selection::iterator tmp
= i
;
1848 (*i
)->set_selected (false);
1849 _selection
.erase (i
);
1858 /* don't bother with removing this regionview from the editor selection,
1859 since we're about to add another note, and thus put/keep this
1860 regionview in the editor selection.
1863 if (!ev
->selected()) {
1864 add_to_selection (ev
);
1869 MidiRegionView::select_all_notes ()
1873 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1874 add_to_selection (*i
);
1879 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1881 uint8_t low_note
= 127;
1882 uint8_t high_note
= 0;
1883 MidiModel::Notes
& notes (_model
->notes());
1884 _optimization_iterator
= _events
.begin();
1890 if (extend
&& _selection
.empty()) {
1896 /* scan existing selection to get note range */
1898 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1899 if ((*i
)->note()->note() < low_note
) {
1900 low_note
= (*i
)->note()->note();
1902 if ((*i
)->note()->note() > high_note
) {
1903 high_note
= (*i
)->note()->note();
1907 low_note
= min (low_note
, notenum
);
1908 high_note
= max (high_note
, notenum
);
1911 no_sound_notes
= true;
1913 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1915 boost::shared_ptr
<NoteType
> note (*n
);
1916 CanvasNoteEvent
* cne
;
1917 bool select
= false;
1919 if (((1 << note
->channel()) & channel_mask
) != 0) {
1921 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1924 } else if (note
->note() == notenum
) {
1930 if ((cne
= find_canvas_note (note
)) != 0) {
1931 // extend is false because we've taken care of it,
1932 // since it extends by time range, not pitch.
1933 note_selected (cne
, add
, false);
1937 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1941 no_sound_notes
= false;
1945 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
1947 MidiModel::Notes
& notes (_model
->notes());
1948 _optimization_iterator
= _events
.begin();
1950 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1952 boost::shared_ptr
<NoteType
> note (*n
);
1953 CanvasNoteEvent
* cne
;
1955 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
1956 if ((cne
= find_canvas_note (note
)) != 0) {
1957 if (cne
->selected()) {
1958 note_deselected (cne
);
1960 note_selected (cne
, true, false);
1968 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
1971 clear_selection_except(ev
);
1976 if (!ev
->selected()) {
1977 add_to_selection (ev
);
1981 /* find end of latest note selected, select all between that and the start of "ev" */
1983 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
1984 Evoral::MusicalTime latest
= 0;
1986 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1987 if ((*i
)->note()->end_time() > latest
) {
1988 latest
= (*i
)->note()->end_time();
1990 if ((*i
)->note()->time() < earliest
) {
1991 earliest
= (*i
)->note()->time();
1995 if (ev
->note()->end_time() > latest
) {
1996 latest
= ev
->note()->end_time();
1999 if (ev
->note()->time() < earliest
) {
2000 earliest
= ev
->note()->time();
2003 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2005 /* find notes entirely within OR spanning the earliest..latest range */
2007 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
2008 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
2009 add_to_selection (*i
);
2013 /* if events were guaranteed to be time sorted, we could do this.
2014 but as of sept 10th 2009, they no longer are.
2017 if ((*i
)->note()->time() > latest
) {
2026 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
2028 remove_from_selection (ev
);
2032 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
2042 // TODO: Make this faster by storing the last updated selection rect, and only
2043 // adjusting things that are in the area that appears/disappeared.
2044 // We probably need a tree to be able to find events in O(log(n)) time.
2046 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2048 /* check if any corner of the note is inside the rect
2051 1) this is computing "touched by", not "contained by" the rect.
2052 2) this does not require that events be sorted in time.
2055 const double ix1
= (*i
)->x1();
2056 const double ix2
= (*i
)->x2();
2057 const double iy1
= (*i
)->y1();
2058 const double iy2
= (*i
)->y2();
2060 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2061 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
2062 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2063 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
2066 if (!(*i
)->selected()) {
2067 add_to_selection (*i
);
2069 } else if ((*i
)->selected()) {
2070 // Not inside rectangle
2071 remove_from_selection (*i
);
2077 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
2079 Selection::iterator i
= _selection
.find (ev
);
2081 if (i
!= _selection
.end()) {
2082 _selection
.erase (i
);
2085 ev
->set_selected (false);
2086 ev
->hide_velocity ();
2088 if (_selection
.empty()) {
2089 PublicEditor
& editor (trackview
.editor());
2090 editor
.get_selection().remove (this);
2095 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
2097 bool add_mrv_selection
= false;
2099 if (_selection
.empty()) {
2100 add_mrv_selection
= true;
2103 if (_selection
.insert (ev
).second
) {
2104 ev
->set_selected (true);
2105 play_midi_note ((ev
)->note());
2108 if (add_mrv_selection
) {
2109 PublicEditor
& editor (trackview
.editor());
2110 editor
.get_selection().add (this);
2115 MidiRegionView::move_selection(double dx
, double dy
, double cumulative_dy
)
2117 typedef vector
<boost::shared_ptr
<NoteType
> > PossibleChord
;
2118 PossibleChord to_play
;
2119 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2121 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2122 if ((*i
)->note()->time() < earliest
) {
2123 earliest
= (*i
)->note()->time();
2127 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2128 if (Evoral::musical_time_equal ((*i
)->note()->time(), earliest
)) {
2129 to_play
.push_back ((*i
)->note());
2131 (*i
)->move_event(dx
, dy
);
2134 if (dy
&& !_selection
.empty() && !no_sound_notes
&& trackview
.editor().sound_notes()) {
2136 if (to_play
.size() > 1) {
2138 PossibleChord shifted
;
2140 for (PossibleChord::iterator n
= to_play
.begin(); n
!= to_play
.end(); ++n
) {
2141 boost::shared_ptr
<NoteType
> moved_note (new NoteType (**n
));
2142 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2143 shifted
.push_back (moved_note
);
2146 play_midi_chord (shifted
);
2148 } else if (!to_play
.empty()) {
2150 boost::shared_ptr
<NoteType
> moved_note (new NoteType (*to_play
.front()));
2151 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2152 play_midi_note (moved_note
);
2158 MidiRegionView::note_dropped(CanvasNoteEvent
*, frameoffset_t dt
, int8_t dnote
)
2160 assert (!_selection
.empty());
2162 uint8_t lowest_note_in_selection
= 127;
2163 uint8_t highest_note_in_selection
= 0;
2164 uint8_t highest_note_difference
= 0;
2166 // find highest and lowest notes first
2168 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2169 uint8_t pitch
= (*i
)->note()->note();
2170 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
2171 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
2175 cerr << "dnote: " << (int) dnote << endl;
2176 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2177 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2178 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2179 << int(highest_note_in_selection) << endl;
2180 cerr << "selection size: " << _selection.size() << endl;
2181 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2184 // Make sure the note pitch does not exceed the MIDI standard range
2185 if (highest_note_in_selection
+ dnote
> 127) {
2186 highest_note_difference
= highest_note_in_selection
- 127;
2189 start_note_diff_command (_("move notes"));
2191 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
2193 Evoral::MusicalTime new_time
= frames_to_beats (beats_to_frames ((*i
)->note()->time()) + dt
);
2199 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2201 uint8_t original_pitch
= (*i
)->note()->note();
2202 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
2204 // keep notes in standard midi range
2205 clamp_to_0_127(new_pitch
);
2207 // keep original pitch if note is dragged outside valid midi range
2208 if ((original_pitch
!= 0 && new_pitch
== 0)
2209 || (original_pitch
!= 127 && new_pitch
== 127)) {
2210 new_pitch
= original_pitch
;
2213 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2214 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2216 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::NoteNumber
, new_pitch
);
2221 // care about notes being moved beyond the upper/lower bounds on the canvas
2222 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2223 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2224 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2229 MidiRegionView::snap_pixel_to_frame(double x
)
2231 PublicEditor
& editor (trackview
.editor());
2232 return snap_frame_to_frame (editor
.pixel_to_frame (x
));
2235 /** Snap a frame offset within our region using the current snap settings.
2236 * @param x Frame offset from this region's position.
2237 * @return Snapped frame offset from this region's position.
2240 MidiRegionView::snap_frame_to_frame (frameoffset_t x
)
2242 PublicEditor
& editor
= trackview
.editor();
2244 /* x is region relative, convert it to global absolute frames */
2245 framepos_t
const session_frame
= x
+ _region
->position();
2247 /* try a snap in either direction */
2248 framepos_t frame
= session_frame
;
2249 editor
.snap_to (frame
, 0);
2251 /* if we went off the beginning of the region, snap forwards */
2252 if (frame
< _region
->position ()) {
2253 frame
= session_frame
;
2254 editor
.snap_to (frame
, 1);
2257 /* back to region relative */
2258 return frame
- _region
->position();
2262 MidiRegionView::snap_to_pixel(double x
)
2264 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2268 MidiRegionView::get_position_pixels()
2270 framepos_t region_frame
= get_position();
2271 return trackview
.editor().frame_to_pixel(region_frame
);
2275 MidiRegionView::get_end_position_pixels()
2277 framepos_t frame
= get_position() + get_duration ();
2278 return trackview
.editor().frame_to_pixel(frame
);
2282 MidiRegionView::beats_to_frames(double beats
) const
2284 return _time_converter
.to(beats
);
2288 MidiRegionView::frames_to_beats(framepos_t frames
) const
2290 return _time_converter
.from(frames
);
2294 MidiRegionView::begin_resizing (bool /*at_front*/)
2296 _resize_data
.clear();
2298 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2299 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2301 // only insert CanvasNotes into the map
2303 NoteResizeData
*resize_data
= new NoteResizeData();
2304 resize_data
->canvas_note
= note
;
2306 // create a new SimpleRect from the note which will be the resize preview
2307 SimpleRect
*resize_rect
= new SimpleRect(
2308 *_note_group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2310 // calculate the colors: get the color settings
2311 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2312 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2315 // make the resize preview notes more transparent and bright
2316 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2318 // calculate color based on note velocity
2319 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2320 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2324 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2325 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2327 resize_data
->resize_rect
= resize_rect
;
2328 _resize_data
.push_back(resize_data
);
2333 /** Update resizing notes while user drags.
2334 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2335 * @param at_front which end of the note (true == note on, false == note off)
2336 * @param delta_x change in mouse position since the start of the drag
2337 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2338 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2339 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2340 * as the \a primary note.
2343 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2345 bool cursor_set
= false;
2347 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2348 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2349 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2354 current_x
= canvas_note
->x1() + delta_x
;
2356 current_x
= primary
->x1() + delta_x
;
2360 current_x
= canvas_note
->x2() + delta_x
;
2362 current_x
= primary
->x2() + delta_x
;
2367 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2368 resize_rect
->property_x2() = canvas_note
->x2();
2370 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2371 resize_rect
->property_x1() = canvas_note
->x1();
2377 beats
= snap_pixel_to_frame (current_x
);
2378 beats
= frames_to_beats (beats
);
2383 if (beats
< canvas_note
->note()->end_time()) {
2384 len
= canvas_note
->note()->time() - beats
;
2385 len
+= canvas_note
->note()->length();
2390 if (beats
>= canvas_note
->note()->time()) {
2391 len
= beats
- canvas_note
->note()->time();
2398 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2399 show_verbose_cursor (buf
, 0, 0);
2408 /** Finish resizing notes when the user releases the mouse button.
2409 * Parameters the same as for \a update_resizing().
2412 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2414 start_note_diff_command (_("resize notes"));
2416 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2417 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2418 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2423 current_x
= canvas_note
->x1() + delta_x
;
2425 current_x
= primary
->x1() + delta_x
;
2429 current_x
= canvas_note
->x2() + delta_x
;
2431 current_x
= primary
->x2() + delta_x
;
2435 current_x
= snap_pixel_to_frame (current_x
);
2436 current_x
= frames_to_beats (current_x
);
2438 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2439 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::StartTime
, current_x
);
2441 double len
= canvas_note
->note()->time() - current_x
;
2442 len
+= canvas_note
->note()->length();
2445 /* XXX convert to beats */
2446 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2451 double len
= current_x
- canvas_note
->note()->time();
2454 /* XXX convert to beats */
2455 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2463 _resize_data
.clear();
2468 MidiRegionView::change_note_channel (CanvasNoteEvent
* event
, int8_t channel
)
2470 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Channel
, (uint8_t) channel
);
2474 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2476 uint8_t new_velocity
;
2479 new_velocity
= event
->note()->velocity() + velocity
;
2480 clamp_to_0_127(new_velocity
);
2482 new_velocity
= velocity
;
2485 event
->set_selected (event
->selected()); // change color
2487 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Velocity
, new_velocity
);
2491 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2496 new_note
= event
->note()->note() + note
;
2501 clamp_to_0_127 (new_note
);
2502 note_diff_add_change (event
, MidiModel::NoteDiffCommand::NoteNumber
, new_note
);
2506 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2508 bool change_start
= false;
2509 bool change_length
= false;
2510 Evoral::MusicalTime new_start
= 0;
2511 Evoral::MusicalTime new_length
= 0;
2513 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2515 front_delta: if positive - move the start of the note later in time (shortening it)
2516 if negative - move the start of the note earlier in time (lengthening it)
2518 end_delta: if positive - move the end of the note later in time (lengthening it)
2519 if negative - move the end of the note earlier in time (shortening it)
2523 if (front_delta
< 0) {
2525 if (event
->note()->time() < -front_delta
) {
2528 new_start
= event
->note()->time() + front_delta
; // moves earlier
2531 /* start moved toward zero, so move the end point out to where it used to be.
2532 Note that front_delta is negative, so this increases the length.
2535 new_length
= event
->note()->length() - front_delta
;
2536 change_start
= true;
2537 change_length
= true;
2541 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2543 if (new_pos
< event
->note()->end_time()) {
2544 new_start
= event
->note()->time() + front_delta
;
2545 /* start moved toward the end, so move the end point back to where it used to be */
2546 new_length
= event
->note()->length() - front_delta
;
2547 change_start
= true;
2548 change_length
= true;
2555 bool can_change
= true;
2556 if (end_delta
< 0) {
2557 if (event
->note()->length() < -end_delta
) {
2563 new_length
= event
->note()->length() + end_delta
;
2564 change_length
= true;
2569 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_start
);
2572 if (change_length
) {
2573 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, new_length
);
2578 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2580 Evoral::MusicalTime new_time
;
2584 if (event
->note()->time() < -delta
) {
2587 new_time
= event
->note()->time() + delta
;
2590 new_time
= event
->note()->time() + delta
;
2596 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2600 MidiRegionView::change_note_length (CanvasNoteEvent
* event
, Evoral::MusicalTime t
)
2602 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, t
);
2606 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2610 if (_selection
.empty()) {
2625 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2626 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2632 start_note_diff_command (_("change velocities"));
2634 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2635 Selection::iterator next
= i
;
2637 change_note_velocity (*i
, delta
, true);
2643 if (!_selection
.empty()) {
2645 snprintf (buf
, sizeof (buf
), "Vel %d",
2646 (int) (*_selection
.begin())->note()->velocity());
2647 show_verbose_cursor (buf
, 10, 10);
2653 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2655 if (_selection
.empty()) {
2672 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2674 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2678 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2685 start_note_diff_command (_("transpose"));
2687 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2688 Selection::iterator next
= i
;
2690 change_note_note (*i
, delta
, true);
2698 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, Evoral::MusicalTime delta
, bool start
, bool end
)
2704 /* grab the current grid distance */
2706 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2708 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2709 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2719 start_note_diff_command (_("change note lengths"));
2721 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2722 Selection::iterator next
= i
;
2725 /* note the negation of the delta for start */
2727 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2736 MidiRegionView::nudge_notes (bool forward
)
2738 if (_selection
.empty()) {
2742 /* pick a note as the point along the timeline to get the nudge distance.
2743 its not necessarily the earliest note, so we may want to pull the notes out
2744 into a vector and sort before using the first one.
2747 framepos_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2749 framepos_t distance
;
2751 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2753 /* grid is off - use nudge distance */
2755 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2761 framepos_t next_pos
= ref_point
;
2764 if (max_framepos
- 1 < next_pos
) {
2768 if (next_pos
== 0) {
2774 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2775 distance
= ref_point
- next_pos
;
2778 if (distance
== 0) {
2782 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2788 start_note_diff_command (_("nudge"));
2790 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2791 Selection::iterator next
= i
;
2793 change_note_time (*i
, delta
, true);
2801 MidiRegionView::change_channel(uint8_t channel
)
2803 start_note_diff_command(_("change channel"));
2804 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2805 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::Channel
, channel
);
2813 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2815 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2817 pre_enter_cursor
= editor
->get_canvas_cursor ();
2819 if (_mouse_state
== SelectTouchDragging
) {
2820 note_selected (ev
, true);
2823 show_verbose_cursor (ev
->note ());
2827 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2829 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2831 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2832 (*i
)->hide_velocity ();
2835 editor
->verbose_cursor()->hide ();
2837 if (pre_enter_cursor
) {
2838 editor
->set_canvas_cursor (pre_enter_cursor
);
2839 pre_enter_cursor
= 0;
2844 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange
* ev
)
2847 s
<< ((int) ev
->patch()->program() + 1) << ":" << (ev
->patch()->bank() + 1);
2848 show_verbose_cursor (s
.str(), 10, 20);
2852 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange
*)
2854 trackview
.editor().verbose_cursor()->hide ();
2858 MidiRegionView::note_mouse_position (float x_fraction
, float /*y_fraction*/, bool can_set_cursor
)
2860 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2862 if (x_fraction
> 0.0 && x_fraction
< 0.25) {
2863 editor
->set_canvas_cursor (editor
->cursors()->left_side_trim
);
2864 } else if (x_fraction
>= 0.75 && x_fraction
< 1.0) {
2865 editor
->set_canvas_cursor (editor
->cursors()->right_side_trim
);
2867 if (pre_enter_cursor
&& can_set_cursor
) {
2868 editor
->set_canvas_cursor (pre_enter_cursor
);
2874 MidiRegionView::set_frame_color()
2878 TimeAxisViewItem::set_frame_color ();
2885 f
= ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2886 } else if (high_enough_for_name
) {
2887 f
= ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2892 if (!rect_visible
) {
2893 f
= UINT_RGBA_CHANGE_A (f
, 0);
2896 frame
->property_fill_color_rgba() = f
;
2900 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2904 case FilterChannels
:
2905 _force_channel
= -1;
2908 _force_channel
= mask
;
2909 mask
= 0xFFFF; // Show all notes as active (below)
2912 // Update notes for selection
2913 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2914 (*i
)->on_channel_selection_change(mask
);
2917 _last_channel_selection
= mask
;
2919 _patch_changes
.clear ();
2920 display_patch_changes ();
2924 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2926 _model_name
= model
;
2927 _custom_device_mode
= custom_device_mode
;
2932 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
2934 if (_selection
.empty()) {
2938 PublicEditor
& editor (trackview
.editor());
2943 editor
.get_cut_buffer().add (selection_as_cut_buffer());
2951 start_note_diff_command();
2953 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2959 note_diff_remove_note (*i
);
2969 MidiRegionView::selection_as_cut_buffer () const
2973 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2974 NoteType
* n
= (*i
)->note().get();
2975 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
2978 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
2984 /** This method handles undo */
2986 MidiRegionView::paste (framepos_t pos
, float times
, const MidiCutBuffer
& mcb
)
2992 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("MIDI paste @ %1 times %2\n", pos
, times
));
2994 trackview
.session()->begin_reversible_command (_("paste"));
2996 start_note_diff_command (_("paste"));
2998 Evoral::MusicalTime beat_delta
;
2999 Evoral::MusicalTime paste_pos_beats
;
3000 Evoral::MusicalTime duration
;
3001 Evoral::MusicalTime end_point
= 0;
3003 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
3004 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
3005 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
3006 paste_pos_beats
= 0;
3008 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",
3009 (*mcb
.notes().begin())->time(),
3010 (*mcb
.notes().rbegin())->end_time(),
3011 duration
, pos
, _region
->position(),
3012 paste_pos_beats
, beat_delta
));
3016 for (int n
= 0; n
< (int) times
; ++n
) {
3018 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
3020 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
3021 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
3023 /* make all newly added notes selected */
3025 note_diff_add_note (copied_note
, true);
3026 end_point
= copied_note
->end_time();
3029 paste_pos_beats
+= duration
;
3032 /* if we pasted past the current end of the region, extend the region */
3034 framepos_t end_frame
= _region
->position() + beats_to_frames (end_point
);
3035 framepos_t region_end
= _region
->position() + _region
->length() - 1;
3037 if (end_frame
> region_end
) {
3039 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste extended region from %1 to %2\n", region_end
, end_frame
));
3041 _region
->clear_changes ();
3042 _region
->set_length (end_frame
, this);
3043 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
3048 trackview
.session()->commit_reversible_command ();
3051 struct EventNoteTimeEarlyFirstComparator
{
3052 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
3053 return a
->note()->time() < b
->note()->time();
3058 MidiRegionView::time_sort_events ()
3060 if (!_sort_needed
) {
3064 EventNoteTimeEarlyFirstComparator cmp
;
3067 _sort_needed
= false;
3071 MidiRegionView::goto_next_note ()
3073 // framepos_t pos = -1;
3074 bool use_next
= false;
3076 if (_events
.back()->selected()) {
3080 time_sort_events ();
3082 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3083 if ((*i
)->selected()) {
3086 } else if (use_next
) {
3088 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3093 /* use the first one */
3095 unique_select (_events
.front());
3100 MidiRegionView::goto_previous_note ()
3102 // framepos_t pos = -1;
3103 bool use_next
= false;
3105 if (_events
.front()->selected()) {
3109 time_sort_events ();
3111 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
3112 if ((*i
)->selected()) {
3115 } else if (use_next
) {
3117 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3122 /* use the last one */
3124 unique_select (*(_events
.rbegin()));
3128 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
3130 bool had_selected
= false;
3132 time_sort_events ();
3134 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3135 if ((*i
)->selected()) {
3136 selected
.insert ((*i
)->note());
3137 had_selected
= true;
3141 if (allow_all_if_none_selected
&& !had_selected
) {
3142 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3143 selected
.insert ((*i
)->note());
3149 MidiRegionView::update_ghost_note (double x
, double y
)
3154 _note_group
->w2i (x
, y
);
3155 framepos_t
const f
= snap_pixel_to_frame (x
);
3158 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
3164 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
3166 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
3167 _ghost_note
->note()->set_length (length
);
3168 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
3170 /* the ghost note does not appear in ghost regions, so pass false in here */
3171 update_note (_ghost_note
, false);
3173 show_verbose_cursor (_ghost_note
->note ());
3177 MidiRegionView::create_ghost_note (double x
, double y
)
3182 boost::shared_ptr
<NoteType
> g (new NoteType
);
3183 _ghost_note
= new NoEventCanvasNote (*this, *_note_group
, g
);
3184 _ghost_note
->property_outline_color_rgba() = 0x000000aa;
3185 update_ghost_note (x
, y
);
3186 _ghost_note
->show ();
3191 show_verbose_cursor (_ghost_note
->note ());
3195 MidiRegionView::snap_changed ()
3201 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
3205 MidiRegionView::drop_down_keys ()
3207 _mouse_state
= None
;
3211 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double /*x*/, double y
)
3213 double note
= midi_stream_view()->y_to_note(y
);
3215 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3217 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
3219 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
3220 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3221 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
3222 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3227 bool add_mrv_selection
= false;
3229 if (_selection
.empty()) {
3230 add_mrv_selection
= true;
3233 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
3234 if (_selection
.insert (*i
).second
) {
3235 (*i
)->set_selected (true);
3239 if (add_mrv_selection
) {
3240 PublicEditor
& editor (trackview
.editor());
3241 editor
.get_selection().add (this);
3246 MidiRegionView::color_handler ()
3248 RegionView::color_handler ();
3250 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3251 (*i
)->set_selected ((*i
)->selected()); // will change color
3254 /* XXX probably more to do here */
3258 MidiRegionView::enable_display (bool yn
)
3260 RegionView::enable_display (yn
);
3267 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos
)
3269 if (_step_edit_cursor
== 0) {
3270 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
3272 _step_edit_cursor
= new ArdourCanvas::SimpleRect (*group
);
3273 _step_edit_cursor
->property_y1() = 0;
3274 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
3275 _step_edit_cursor
->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3276 _step_edit_cursor
->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3279 move_step_edit_cursor (pos
);
3280 _step_edit_cursor
->show ();
3284 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos
)
3286 _step_edit_cursor_position
= pos
;
3288 if (_step_edit_cursor
) {
3289 double pixel
= trackview
.editor().frame_to_pixel (beats_to_frames (pos
));
3290 _step_edit_cursor
->property_x1() = pixel
;
3291 set_step_edit_cursor_width (_step_edit_cursor_width
);
3296 MidiRegionView::hide_step_edit_cursor ()
3298 if (_step_edit_cursor
) {
3299 _step_edit_cursor
->hide ();
3304 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats
)
3306 _step_edit_cursor_width
= beats
;
3308 if (_step_edit_cursor
) {
3309 _step_edit_cursor
->property_x2() = _step_edit_cursor
->property_x1() + trackview
.editor().frame_to_pixel (beats_to_frames (beats
));
3313 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3314 * @param buf Data that has been recorded.
3315 * @param w Source that this data will end up in.
3318 MidiRegionView::data_recorded (boost::shared_ptr
<MidiBuffer
> buf
, boost::weak_ptr
<MidiSource
> w
)
3320 if (!_active_notes
) {
3321 /* we aren't actively being recorded to */
3325 boost::shared_ptr
<MidiSource
> src
= w
.lock ();
3326 if (!src
|| src
!= midi_region()->midi_source()) {
3327 /* recorded data was not destined for our source */
3331 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*> (&trackview
);
3332 BeatsFramesConverter
converter (trackview
.session()->tempo_map(), mtv
->midi_track()->get_capture_start_frame (0));
3334 framepos_t back
= max_framepos
;
3336 for (MidiBuffer::iterator i
= buf
->begin(); i
!= buf
->end(); ++i
) {
3337 Evoral::MIDIEvent
<MidiBuffer::TimeType
> const ev (*i
, false);
3338 assert (ev
.buffer ());
3340 Evoral::MusicalTime
const time_beats
= converter
.from (ev
.time () - converter
.origin_b ());
3342 if (ev
.type() == MIDI_CMD_NOTE_ON
) {
3344 boost::shared_ptr
<NoteType
> note (
3345 new NoteType (ev
.channel(), time_beats
, 0, ev
.note(), ev
.velocity())
3348 add_note (note
, true);
3350 /* fix up our note range */
3351 if (ev
.note() < _current_range_min
) {
3352 midi_stream_view()->apply_note_range (ev
.note(), _current_range_max
, true);
3353 } else if (ev
.note() > _current_range_max
) {
3354 midi_stream_view()->apply_note_range (_current_range_min
, ev
.note(), true);
3357 } else if (ev
.type() == MIDI_CMD_NOTE_OFF
) {
3358 resolve_note (ev
.note (), time_beats
);
3364 midi_stream_view()->check_record_layers (region(), back
);
3368 MidiRegionView::trim_front_starting ()
3370 /* Reparent the note group to the region view's parent, so that it doesn't change
3371 when the region view is trimmed.
3373 _temporary_note_group
= new ArdourCanvas::Group (*group
->property_parent ());
3374 _temporary_note_group
->move (group
->property_x(), group
->property_y());
3375 _note_group
->reparent (*_temporary_note_group
);
3379 MidiRegionView::trim_front_ending ()
3381 _note_group
->reparent (*group
);
3382 delete _temporary_note_group
;
3383 _temporary_note_group
= 0;
3385 if (_region
->start() < 0) {
3386 /* Trim drag made start time -ve; fix this */
3387 midi_region()->fix_negative_start ();
3391 /** @return channel (counted from 0) to add an event to, based on the current setting
3392 * of the channel selector.
3395 MidiRegionView::get_channel_for_add () const
3397 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3398 uint16_t const chn_mask
= mtv
->channel_selector().get_selected_channels();
3400 uint8_t channel
= 0;
3402 /* pick the highest selected channel, unless all channels are selected,
3403 which is interpreted to mean channel 1 (zero)
3406 for (uint16_t i
= 0; i
< 16; ++i
) {
3407 if (chn_mask
& (1<<i
)) {
3413 if (chn_cnt
== 16) {
3421 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange
* pc
)
3423 PatchChangeDialog
d (&_time_converter
, trackview
.session(), *pc
->patch (), Gtk::Stock::APPLY
);
3424 if (d
.run () != Gtk::RESPONSE_ACCEPT
) {
3428 change_patch_change (pc
->patch(), d
.patch ());
3433 MidiRegionView::show_verbose_cursor (boost::shared_ptr
<NoteType
> n
) const
3436 snprintf (buf
, sizeof (buf
), "%s (%d) Chn %d\nVel %d",
3437 Evoral::midi_note_name (n
->note()).c_str(),
3439 (int) n
->channel() + 1,
3440 (int) n
->velocity());
3442 show_verbose_cursor (buf
, 10, 20);
3446 MidiRegionView::show_verbose_cursor (string
const & text
, double xoffset
, double yoffset
) const
3450 trackview
.editor().get_pointer_position (wx
, wy
);
3455 trackview
.editor().verbose_cursor()->set (text
, wx
, wy
);
3456 trackview
.editor().verbose_cursor()->show ();