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"
75 using namespace ARDOUR
;
77 using namespace Editing
;
78 using namespace ArdourCanvas
;
79 using Gtkmm2ext::Keyboard
;
81 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
82 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
const & basic_color
)
83 : RegionView (parent
, tv
, r
, spu
, basic_color
)
85 , _last_channel_selection(0xFFFF)
86 , _current_range_min(0)
87 , _current_range_max(0)
88 , _model_name(string())
89 , _custom_device_mode(string())
91 , _note_group(new ArdourCanvas::Group(*group
))
92 , _note_diff_command (0)
95 , _step_edit_cursor (0)
96 , _step_edit_cursor_width (1.0)
97 , _step_edit_cursor_position (0.0)
98 , _channel_selection_scoped_note (0)
99 , _temporary_note_group (0)
102 , _sort_needed (true)
103 , _optimization_iterator (_events
.end())
105 , no_sound_notes (false)
108 , pre_enter_cursor (0)
110 _note_group
->raise_to_top();
111 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
113 connect_to_diskstream ();
116 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
117 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
& basic_color
,
118 TimeAxisViewItem::Visibility visibility
)
119 : RegionView (parent
, tv
, r
, spu
, basic_color
, false, visibility
)
121 , _last_channel_selection(0xFFFF)
122 , _model_name(string())
123 , _custom_device_mode(string())
125 , _note_group(new ArdourCanvas::Group(*parent
))
126 , _note_diff_command (0)
129 , _step_edit_cursor (0)
130 , _step_edit_cursor_width (1.0)
131 , _step_edit_cursor_position (0.0)
132 , _channel_selection_scoped_note (0)
133 , _temporary_note_group (0)
136 , _sort_needed (true)
137 , _optimization_iterator (_events
.end())
139 , no_sound_notes (false)
143 _note_group
->raise_to_top();
144 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
146 connect_to_diskstream ();
149 MidiRegionView::MidiRegionView (const MidiRegionView
& other
)
150 : sigc::trackable(other
)
153 , _last_channel_selection(0xFFFF)
154 , _model_name(string())
155 , _custom_device_mode(string())
157 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
158 , _note_diff_command (0)
161 , _step_edit_cursor (0)
162 , _step_edit_cursor_width (1.0)
163 , _step_edit_cursor_position (0.0)
164 , _channel_selection_scoped_note (0)
165 , _temporary_note_group (0)
168 , _sort_needed (true)
169 , _optimization_iterator (_events
.end())
171 , no_sound_notes (false)
178 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
179 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
184 MidiRegionView::MidiRegionView (const MidiRegionView
& other
, boost::shared_ptr
<MidiRegion
> region
)
185 : RegionView (other
, boost::shared_ptr
<Region
> (region
))
187 , _last_channel_selection(0xFFFF)
188 , _model_name(string())
189 , _custom_device_mode(string())
191 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
192 , _note_diff_command (0)
195 , _step_edit_cursor (0)
196 , _step_edit_cursor_width (1.0)
197 , _step_edit_cursor_position (0.0)
198 , _channel_selection_scoped_note (0)
199 , _temporary_note_group (0)
202 , _sort_needed (true)
203 , _optimization_iterator (_events
.end())
205 , no_sound_notes (false)
212 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
213 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
219 MidiRegionView::init (Gdk::Color
const & basic_color
, bool wfd
)
221 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
223 CanvasNoteEvent::CanvasNoteEventDeleted
.connect (note_delete_connection
, MISSING_INVALIDATOR
,
224 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection
, this, _1
),
228 midi_region()->midi_source(0)->load_model();
231 _model
= midi_region()->midi_source(0)->model();
232 _enable_display
= false;
234 RegionView::init (basic_color
, false);
236 compute_colors (basic_color
);
238 set_height (trackview
.current_height());
241 region_sync_changed ();
242 region_resized (ARDOUR::bounds_change
);
245 reset_width_dependent_items (_pixel_width
);
249 _enable_display
= true;
252 display_model (_model
);
256 group
->raise_to_top();
257 group
->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event
), false);
259 midi_view()->signal_channel_mode_changed().connect(
260 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed
));
262 midi_view()->signal_midi_patch_settings_changed().connect(
263 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed
));
265 trackview
.editor().SnapChanged
.connect (snap_changed_connection
, invalidator (*this), ui_bind (&MidiRegionView::snap_changed
, this), gui_context ());
267 connect_to_diskstream ();
271 MidiRegionView::connect_to_diskstream ()
273 midi_view()->midi_track()->DataRecorded
.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::data_recorded
, this, _1
, _2
), gui_context ());
277 MidiRegionView::canvas_event(GdkEvent
* ev
)
280 case GDK_ENTER_NOTIFY
:
281 case GDK_LEAVE_NOTIFY
:
282 _last_event_x
= ev
->crossing
.x
;
283 _last_event_y
= ev
->crossing
.y
;
285 case GDK_MOTION_NOTIFY
:
286 _last_event_x
= ev
->motion
.x
;
287 _last_event_y
= ev
->motion
.y
;
293 if (!trackview
.editor().internal_editing()) {
299 return scroll (&ev
->scroll
);
302 return key_press (&ev
->key
);
304 case GDK_KEY_RELEASE
:
305 return key_release (&ev
->key
);
307 case GDK_BUTTON_PRESS
:
308 return button_press (&ev
->button
);
310 case GDK_2BUTTON_PRESS
:
313 case GDK_BUTTON_RELEASE
:
314 return button_release (&ev
->button
);
316 case GDK_ENTER_NOTIFY
:
317 return enter_notify (&ev
->crossing
);
319 case GDK_LEAVE_NOTIFY
:
320 return leave_notify (&ev
->crossing
);
322 case GDK_MOTION_NOTIFY
:
323 return motion (&ev
->motion
);
333 MidiRegionView::remove_ghost_note ()
340 MidiRegionView::enter_notify (GdkEventCrossing
* ev
)
342 trackview
.editor().MouseModeChanged
.connect (
343 _mouse_mode_connection
, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed
, this), gui_context ()
346 Keyboard::magic_widget_grab_focus();
349 if (trackview
.editor().current_mouse_mode() == MouseRange
) {
350 create_ghost_note (ev
->x
, ev
->y
);
357 MidiRegionView::leave_notify (GdkEventCrossing
*)
359 _mouse_mode_connection
.disconnect ();
361 trackview
.editor().hide_verbose_canvas_cursor ();
362 remove_ghost_note ();
367 MidiRegionView::mouse_mode_changed ()
369 if (trackview
.editor().current_mouse_mode() == MouseRange
&& trackview
.editor().internal_editing()) {
370 create_ghost_note (_last_event_x
, _last_event_y
);
372 remove_ghost_note ();
373 trackview
.editor().hide_verbose_canvas_cursor ();
378 MidiRegionView::button_press (GdkEventButton
* ev
)
380 if (ev
->button
!= 1) {
387 group
->w2i (_last_x
, _last_y
);
389 if (_mouse_state
!= SelectTouchDragging
) {
391 _pressed_button
= ev
->button
;
392 _mouse_state
= Pressed
;
397 _pressed_button
= ev
->button
;
403 MidiRegionView::button_release (GdkEventButton
* ev
)
405 double event_x
, event_y
;
406 framepos_t event_frame
= 0;
408 if (ev
->button
!= 1) {
415 group
->w2i(event_x
, event_y
);
416 group
->ungrab(ev
->time
);
418 event_frame
= trackview
.editor().pixel_to_frame(event_x
);
420 switch (_mouse_state
) {
421 case Pressed
: // Clicked
423 switch (trackview
.editor().current_mouse_mode()) {
429 if (Keyboard::is_insert_note_event(ev
)){
431 double event_x
, event_y
;
435 group
->w2i(event_x
, event_y
);
438 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
444 create_note_at (event_x
, event_y
, beats
, true);
452 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
458 create_note_at (event_x
, event_y
, beats
, true);
469 case SelectRectDragging
: // Select drag done
476 case AddDragging
: // Add drag done
480 if (Keyboard::is_insert_note_event(ev
) || trackview
.editor().current_mouse_mode() == MouseRange
){
482 if (_drag_rect
->property_x2() > _drag_rect
->property_x1() + 2) {
484 const double x
= _drag_rect
->property_x1();
485 const double length
= trackview
.editor().pixel_to_frame (_drag_rect
->property_x2() - _drag_rect
->property_x1());
487 create_note_at (x
, _drag_rect
->property_y1(), frames_to_beats(length
), true);
494 create_ghost_note (ev
->x
, ev
->y
);
504 MidiRegionView::motion (GdkEventMotion
* ev
)
506 double event_x
, event_y
;
507 framepos_t event_frame
= 0;
511 group
->w2i(event_x
, event_y
);
513 // convert event_x to global frame
514 event_frame
= trackview
.editor().pixel_to_frame(event_x
) + _region
->position();
515 trackview
.editor().snap_to(event_frame
);
517 // convert event_frame back to local coordinates relative to position
518 event_frame
-= _region
->position();
520 if (!_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
521 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())
522 && _mouse_state
!= AddDragging
){
524 create_ghost_note (ev
->x
, ev
->y
);
526 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
527 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())){
529 update_ghost_note (ev
->x
, ev
->y
);
531 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
){
536 trackview
.editor().hide_verbose_canvas_cursor ();
538 else if (_ghost_note
&& trackview
.editor().current_mouse_mode() == MouseRange
) {
539 update_ghost_note (ev
->x
, ev
->y
);
542 /* any motion immediately hides velocity text that may have been visible */
544 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
545 (*i
)->hide_velocity ();
548 switch (_mouse_state
) {
549 case Pressed
: // Maybe start a drag, if we've moved a bit
551 if (fabs (event_x
- _last_x
) < 1 && fabs (event_y
- _last_y
) < 1) {
552 /* no appreciable movement since the button was pressed */
557 if (_pressed_button
== 1 && trackview
.editor().current_mouse_mode() == MouseObject
558 && !Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())) {
560 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
561 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
565 _drag_start_x
= event_x
;
566 _drag_start_y
= event_y
;
568 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
569 _drag_rect
->property_x1() = event_x
;
570 _drag_rect
->property_y1() = event_y
;
571 _drag_rect
->property_x2() = event_x
;
572 _drag_rect
->property_y2() = event_y
;
573 _drag_rect
->property_outline_what() = 0xFF;
574 _drag_rect
->property_outline_color_rgba()
575 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
576 _drag_rect
->property_fill_color_rgba()
577 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
579 _mouse_state
= SelectRectDragging
;
582 // Add note drag start
583 } else if (trackview
.editor().internal_editing()) {
588 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
589 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
593 _drag_start_x
= event_x
;
594 _drag_start_y
= event_y
;
596 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
597 _drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
599 _drag_rect
->property_y1() = midi_stream_view()->note_to_y(
600 midi_stream_view()->y_to_note(event_y
));
601 _drag_rect
->property_x2() = trackview
.editor().frame_to_pixel(event_frame
);
602 _drag_rect
->property_y2() = _drag_rect
->property_y1()
603 + floor(midi_stream_view()->note_height());
604 _drag_rect
->property_outline_what() = 0xFF;
605 _drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
606 _drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
608 _mouse_state
= AddDragging
;
615 trackview
.editor().hide_verbose_canvas_cursor ();
623 case SelectRectDragging
: // Select drag motion
624 case AddDragging
: // Add note drag motion
629 GdkModifierType state
;
630 gdk_window_get_pointer(ev
->window
, &t_x
, &t_y
, &state
);
635 if (_mouse_state
== AddDragging
){
636 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
641 if (event_x
> _drag_start_x
){
642 _drag_rect
->property_x2() = event_x
;
645 _drag_rect
->property_x1() = event_x
;
649 if (_drag_rect
&& _mouse_state
== SelectRectDragging
) {
651 if (event_y
> _drag_start_y
){
652 _drag_rect
->property_y2() = event_y
;
655 _drag_rect
->property_y1() = event_y
;
658 update_drag_selection(_drag_start_x
, event_x
, _drag_start_y
, event_y
);
664 case SelectTouchDragging
:
676 MidiRegionView::scroll (GdkEventScroll
* ev
)
678 if (_selection
.empty()) {
682 trackview
.editor().hide_verbose_canvas_cursor ();
684 bool fine
= !Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
);
686 if (ev
->direction
== GDK_SCROLL_UP
) {
687 change_velocities (true, fine
, false);
688 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
689 change_velocities (false, fine
, false);
695 MidiRegionView::key_press (GdkEventKey
* ev
)
697 /* since GTK bindings are generally activated on press, and since
698 detectable auto-repeat is the name of the game and only sends
699 repeated presses, carry out key actions at key press, not release.
702 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
){
703 _mouse_state
= SelectTouchDragging
;
706 } else if (ev
->keyval
== GDK_Escape
) {
710 } else if (ev
->keyval
== GDK_comma
|| ev
->keyval
== GDK_period
) {
712 bool start
= (ev
->keyval
== GDK_comma
);
713 bool end
= (ev
->keyval
== GDK_period
);
714 bool shorter
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
);
715 bool fine
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
717 change_note_lengths (fine
, shorter
, 0.0, start
, end
);
721 } else if (ev
->keyval
== GDK_Delete
) {
726 } else if (ev
->keyval
== GDK_Tab
) {
728 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
729 goto_previous_note ();
735 } else if (ev
->keyval
== GDK_Up
) {
737 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
738 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
740 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
741 change_velocities (true, fine
, allow_smush
);
743 transpose (true, fine
, allow_smush
);
747 } else if (ev
->keyval
== GDK_Down
) {
749 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
750 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
752 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
753 change_velocities (false, fine
, allow_smush
);
755 transpose (false, fine
, allow_smush
);
759 } else if (ev
->keyval
== GDK_Left
) {
764 } else if (ev
->keyval
== GDK_Right
) {
769 } else if (ev
->keyval
== GDK_Control_L
) {
778 MidiRegionView::key_release (GdkEventKey
* ev
)
780 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
788 MidiRegionView::show_list_editor ()
791 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
793 _list_editor
->present ();
796 /** Add a note to the model, and the view, at a canvas (click) coordinate.
797 * \param x horizontal position in pixels
798 * \param y vertical position in pixels
799 * \param length duration of the note in beats, which will be snapped to the grid
800 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
803 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
805 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
806 MidiStreamView
* const view
= mtv
->midi_view();
808 double note
= view
->y_to_note(y
);
811 assert(note
<= 127.0);
813 // Start of note in frames relative to region start
814 framepos_t
const start_frames
= snap_frame_to_frame(trackview
.editor().pixel_to_frame(x
));
815 assert(start_frames
>= 0);
818 length
= frames_to_beats(
819 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
821 assert (length
!= 0);
824 length
= frames_to_beats (beats_to_frames (length
) - 1);
827 const boost::shared_ptr
<NoteType
> new_note (new NoteType (get_channel_for_add (),
828 frames_to_beats(start_frames
+ _region
->start()), length
,
829 (uint8_t)note
, 0x40));
831 if (_model
->contains (new_note
)) {
835 view
->update_note_range(new_note
->note());
837 MidiModel::NoteDiffCommand
* cmd
= _model
->new_note_diff_command("add note");
839 _model
->apply_command(*trackview
.session(), cmd
);
841 play_midi_note (new_note
);
845 MidiRegionView::clear_events()
850 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
851 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
856 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
861 _patch_changes
.clear();
863 _optimization_iterator
= _events
.end();
867 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
871 content_connection
.disconnect ();
872 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
876 if (_enable_display
) {
882 MidiRegionView::start_note_diff_command (string name
)
884 if (!_note_diff_command
) {
885 _note_diff_command
= _model
->new_note_diff_command (name
);
890 MidiRegionView::note_diff_add_note (const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
892 if (_note_diff_command
) {
893 _note_diff_command
->add (note
);
896 _marked_for_selection
.insert(note
);
899 _marked_for_velocity
.insert(note
);
904 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent
* ev
)
906 if (_note_diff_command
&& ev
->note()) {
907 _note_diff_command
->remove(ev
->note());
912 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
913 MidiModel::NoteDiffCommand::Property property
,
916 if (_note_diff_command
) {
917 _note_diff_command
->change (ev
->note(), property
, val
);
922 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
923 MidiModel::NoteDiffCommand::Property property
,
924 Evoral::MusicalTime val
)
926 if (_note_diff_command
) {
927 _note_diff_command
->change (ev
->note(), property
, val
);
932 MidiRegionView::apply_diff (bool as_subcommand
)
936 if (!_note_diff_command
) {
940 if ((add_or_remove
= _note_diff_command
->adds_or_removes())) {
941 // Mark all selected notes for selection when model reloads
942 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
943 _marked_for_selection
.insert((*i
)->note());
948 _model
->apply_command_as_subcommand (*trackview
.session(), _note_diff_command
);
950 _model
->apply_command (*trackview
.session(), _note_diff_command
);
953 _note_diff_command
= 0;
954 midi_view()->midi_track()->playlist_modified();
957 _marked_for_selection
.clear();
960 _marked_for_velocity
.clear();
964 MidiRegionView::abort_command()
966 delete _note_diff_command
;
967 _note_diff_command
= 0;
972 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
974 if (_optimization_iterator
!= _events
.end()) {
975 ++_optimization_iterator
;
978 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
979 return *_optimization_iterator
;
982 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
983 if ((*_optimization_iterator
)->note() == note
) {
984 return *_optimization_iterator
;
992 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
994 MidiModel::Notes notes
;
995 _model
->get_notes (notes
, op
, val
, chan_mask
);
997 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
998 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
1006 MidiRegionView::redisplay_model()
1008 // Don't redisplay the model if we're currently recording and displaying that
1009 if (_active_notes
) {
1017 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1018 (*i
)->invalidate ();
1021 MidiModel::ReadLock
lock(_model
->read_lock());
1023 MidiModel::Notes
& notes (_model
->notes());
1024 _optimization_iterator
= _events
.begin();
1026 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1028 boost::shared_ptr
<NoteType
> note (*n
);
1029 CanvasNoteEvent
* cne
;
1032 if (note_in_region_range (note
, visible
)) {
1034 if ((cne
= find_canvas_note (note
)) != 0) {
1041 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
1043 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
1055 add_note (note
, visible
);
1060 if ((cne
= find_canvas_note (note
)) != 0) {
1068 /* remove note items that are no longer valid */
1070 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
1071 if (!(*i
)->valid ()) {
1073 i
= _events
.erase (i
);
1079 _patch_changes
.clear();
1083 display_patch_changes ();
1085 _marked_for_selection
.clear ();
1086 _marked_for_velocity
.clear ();
1088 /* we may have caused _events to contain things out of order (e.g. if a note
1089 moved earlier or later). we don't generally need them in time order, but
1090 make a note that a sort is required for those cases that require it.
1093 _sort_needed
= true;
1097 MidiRegionView::display_patch_changes ()
1099 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1100 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
1102 for (uint8_t i
= 0; i
< 16; ++i
) {
1103 if (chn_mask
& (1<<i
)) {
1104 display_patch_changes_on_channel (i
);
1110 MidiRegionView::display_patch_changes_on_channel (uint8_t channel
)
1112 for (MidiModel::PatchChanges::const_iterator i
= _model
->patch_changes().begin(); i
!= _model
->patch_changes().end(); ++i
) {
1114 if ((*i
)->channel() != channel
) {
1118 MIDI::Name::PatchPrimaryKey
patch_key ((*i
)->bank_msb (), (*i
)->bank_lsb (), (*i
)->program ());
1120 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1121 MIDI::Name::MidiPatchManager::instance().find_patch(
1122 _model_name
, _custom_device_mode
, channel
, patch_key
);
1125 add_canvas_patch_change (*i
, patch
->name());
1128 /* program and bank numbers are zero-based: convert to one-based */
1129 snprintf (buf
, 16, "%d\n%d", (*i
)->program() + 1, (*i
)->bank() + 1);
1130 add_canvas_patch_change (*i
, buf
);
1136 MidiRegionView::display_sysexes()
1138 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1139 Evoral::MusicalTime time
= (*i
)->time();
1144 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1145 str
<< int((*i
)->buffer()[b
]);
1146 if (b
!= (*i
)->size() -1) {
1150 string text
= str
.str();
1152 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1154 double height
= midi_stream_view()->contents_height();
1156 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1157 new CanvasSysEx(*this, *_note_group
, text
, height
, x
, 1.0));
1159 // Show unless patch change is beyond the region bounds
1160 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1166 _sys_exes
.push_back(sysex
);
1171 MidiRegionView::~MidiRegionView ()
1173 in_destructor
= true;
1175 trackview
.editor().hide_verbose_canvas_cursor ();
1177 note_delete_connection
.disconnect ();
1179 delete _list_editor
;
1181 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1183 if (_active_notes
) {
1191 delete _note_diff_command
;
1192 delete _step_edit_cursor
;
1193 delete _temporary_note_group
;
1197 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1199 RegionView::region_resized(what_changed
);
1201 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1202 set_duration(_region
->length(), 0);
1203 if (_enable_display
) {
1210 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1212 RegionView::reset_width_dependent_items(pixel_width
);
1213 assert(_pixel_width
== pixel_width
);
1215 if (_enable_display
) {
1219 move_step_edit_cursor (_step_edit_cursor_position
);
1220 set_step_edit_cursor_width (_step_edit_cursor_width
);
1224 MidiRegionView::set_height (double height
)
1226 static const double FUDGE
= 2.0;
1227 const double old_height
= _height
;
1228 RegionView::set_height(height
);
1229 _height
= height
- FUDGE
;
1231 apply_note_range(midi_stream_view()->lowest_note(),
1232 midi_stream_view()->highest_note(),
1233 height
!= old_height
+ FUDGE
);
1236 name_pixbuf
->raise_to_top();
1239 for (PatchChanges::iterator x
= _patch_changes
.begin(); x
!= _patch_changes
.end(); ++x
) {
1240 (*x
)->set_height (midi_stream_view()->contents_height());
1243 if (_step_edit_cursor
) {
1244 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
1249 /** Apply the current note range from the stream view
1250 * by repositioning/hiding notes as necessary
1253 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1255 if (!_enable_display
) {
1259 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1263 _current_range_min
= min
;
1264 _current_range_max
= max
;
1266 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1267 CanvasNoteEvent
* event
= *i
;
1268 boost::shared_ptr
<NoteType
> note (event
->note());
1270 if (note
->note() < _current_range_min
||
1271 note
->note() > _current_range_max
) {
1277 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1279 const double y1
= midi_stream_view()->note_to_y(note
->note());
1280 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1282 cnote
->property_y1() = y1
;
1283 cnote
->property_y2() = y2
;
1285 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1287 const double diamond_size
= update_hit (chit
);
1289 chit
->set_height (diamond_size
);
1295 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1299 double unit_position
= _region
->position () / samples_per_unit
;
1300 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1301 MidiGhostRegion
* ghost
;
1303 if (mtv
&& mtv
->midi_view()) {
1304 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1305 to allow having midi notes on top of note lines and waveforms.
1307 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1309 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1312 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1313 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1314 ghost
->add_note(note
);
1318 ghost
->set_height ();
1319 ghost
->set_duration (_region
->length() / samples_per_unit
);
1320 ghosts
.push_back (ghost
);
1322 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1328 /** Begin tracking note state for successive calls to add_event
1331 MidiRegionView::begin_write()
1333 assert(!_active_notes
);
1334 _active_notes
= new CanvasNote
*[128];
1335 for (unsigned i
=0; i
< 128; ++i
) {
1336 _active_notes
[i
] = 0;
1341 /** Destroy note state for add_event
1344 MidiRegionView::end_write()
1346 delete[] _active_notes
;
1348 _marked_for_selection
.clear();
1349 _marked_for_velocity
.clear();
1353 /** Resolve an active MIDI note (while recording).
1356 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1358 if (midi_view()->note_mode() != Sustained
) {
1362 if (_active_notes
&& _active_notes
[note
]) {
1364 const framepos_t end_time_frames
= beats_to_frames(end_time
);
1366 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1367 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1368 _active_notes
[note
] = 0;
1373 /** Extend active notes to rightmost edge of region (if length is changed)
1376 MidiRegionView::extend_active_notes()
1378 if (!_active_notes
) {
1382 for (unsigned i
=0; i
< 128; ++i
) {
1383 if (_active_notes
[i
]) {
1384 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1391 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1393 if (no_sound_notes
|| !trackview
.editor().sound_notes()) {
1397 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1399 if (!route_ui
|| !route_ui
->midi_track()) {
1403 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1409 MidiRegionView::play_midi_chord (vector
<boost::shared_ptr
<NoteType
> > notes
)
1411 if (no_sound_notes
|| !trackview
.editor().sound_notes()) {
1415 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1417 if (!route_ui
|| !route_ui
->midi_track()) {
1421 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1423 for (vector
<boost::shared_ptr
<NoteType
> >::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1432 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1434 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1436 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1437 (note_start_frames
< _region
->start());
1439 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1440 (note
->note() <= midi_stream_view()->highest_note());
1445 /** Update a canvas note's size from its model note.
1446 * @param ev Canvas note to update.
1447 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1450 MidiRegionView::update_note (CanvasNote
* ev
, bool update_ghost_regions
)
1452 boost::shared_ptr
<NoteType
> note
= ev
->note();
1454 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1456 /* trim note display to not overlap the end of its region */
1457 const framepos_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1459 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1460 const double y1
= midi_stream_view()->note_to_y(note
->note());
1461 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1463 ev
->property_x1() = x
;
1464 ev
->property_y1() = y1
;
1466 if (note
->length() > 0) {
1467 ev
->property_x2() = note_endpixel
;
1469 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1472 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1474 if (note
->length() == 0) {
1475 if (_active_notes
) {
1476 assert(note
->note() < 128);
1477 // If this note is already active there's a stuck note,
1478 // finish the old note rectangle
1479 if (_active_notes
[note
->note()]) {
1480 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1481 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1482 old_rect
->property_x2() = x
;
1483 old_rect
->property_outline_what() = (guint32
) 0xF;
1485 _active_notes
[note
->note()] = ev
;
1487 /* outline all but right edge */
1488 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1490 /* outline all edges */
1491 ev
->property_outline_what() = (guint32
) 0xF;
1494 if (update_ghost_regions
) {
1495 for (std::vector
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
1496 MidiGhostRegion
* gr
= dynamic_cast<MidiGhostRegion
*> (*i
);
1498 gr
->update_note (ev
);
1505 MidiRegionView::update_hit (CanvasHit
* ev
)
1507 boost::shared_ptr
<NoteType
> note
= ev
->note();
1509 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1510 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1511 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1512 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1516 return diamond_size
;
1519 /** Add a MIDI note to the view (with length).
1521 * If in sustained mode, notes with length 0 will be considered active
1522 * notes, and resolve_note should be called when the corresponding note off
1523 * event arrives, to properly display the note.
1526 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1528 CanvasNoteEvent
* event
= 0;
1530 assert(note
->time() >= 0);
1531 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1533 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1535 if (midi_view()->note_mode() == Sustained
) {
1537 CanvasNote
* ev_rect
= new CanvasNote(*this, *_note_group
, note
);
1539 update_note (ev_rect
);
1543 MidiGhostRegion
* gr
;
1545 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1546 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1547 gr
->add_note(ev_rect
);
1551 } else if (midi_view()->note_mode() == Percussive
) {
1553 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1555 CanvasHit
* ev_diamond
= new CanvasHit(*this, *_note_group
, diamond_size
, note
);
1557 update_hit (ev_diamond
);
1566 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1567 note_selected(event
, true);
1570 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1571 event
->show_velocity();
1574 event
->on_channel_selection_change(_last_channel_selection
);
1575 _events
.push_back(event
);
1584 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1585 MidiStreamView
* const view
= mtv
->midi_view();
1587 view
->update_note_range(note
->note());
1591 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1592 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1594 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1596 /* potentially extend region to hold new note */
1598 framepos_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1599 framepos_t region_end
= _region
->position() + _region
->length() - 1;
1601 if (end_frame
> region_end
) {
1602 _region
->set_length (end_frame
- _region
->position(), this);
1605 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1606 MidiStreamView
* const view
= mtv
->midi_view();
1608 view
->update_note_range(new_note
->note());
1610 _marked_for_selection
.clear ();
1613 start_note_diff_command (_("step add"));
1614 note_diff_add_note (new_note
, true, false);
1617 // last_step_edit_note = new_note;
1621 MidiRegionView::step_sustain (Evoral::MusicalTime beats
)
1623 change_note_lengths (false, false, beats
, false, true);
1627 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch
, const string
& displaytext
)
1629 assert (patch
->time() >= 0);
1631 const double x
= trackview
.editor().frame_to_pixel (beats_to_frames (patch
->time()));
1633 double const height
= midi_stream_view()->contents_height();
1635 boost::shared_ptr
<CanvasPatchChange
> patch_change
= boost::shared_ptr
<CanvasPatchChange
>(
1636 new CanvasPatchChange(*this, *_note_group
,
1641 _custom_device_mode
,
1645 // Show unless patch change is beyond the region bounds
1646 if (patch
->time() - _region
->start() >= _region
->length() || patch
->time() < _region
->start()) {
1647 patch_change
->hide();
1649 patch_change
->show();
1652 _patch_changes
.push_back (patch_change
);
1656 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1658 MidiModel::PatchChanges::iterator i
= _model
->patch_change_lower_bound (time
);
1659 while (i
!= _model
->patch_changes().end() && (*i
)->channel() != channel
) {
1663 if (i
!= _model
->patch_changes().end()) {
1664 key
.msb
= (*i
)->bank_msb ();
1665 key
.lsb
= (*i
)->bank_lsb ();
1666 key
.program_number
= (*i
)->program ();
1668 key
.msb
= key
.lsb
= key
.program_number
= 0;
1671 assert (key
.is_sane());
1676 MidiRegionView::change_patch_change (CanvasPatchChange
& pc
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1678 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1680 if (pc
.patch()->program() != new_patch
.program_number
) {
1681 c
->change_program (pc
.patch (), new_patch
.program_number
);
1684 int const new_bank
= (new_patch
.msb
<< 7) | new_patch
.lsb
;
1685 if (pc
.patch()->bank() != new_bank
) {
1686 c
->change_bank (pc
.patch (), new_bank
);
1689 _model
->apply_command (*trackview
.session(), c
);
1691 _patch_changes
.clear ();
1692 display_patch_changes ();
1696 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change
, const Evoral::PatchChange
<Evoral::MusicalTime
> & new_change
)
1698 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1700 if (old_change
->time() != new_change
.time()) {
1701 c
->change_time (old_change
, new_change
.time());
1704 if (old_change
->channel() != new_change
.channel()) {
1705 c
->change_channel (old_change
, new_change
.channel());
1708 if (old_change
->program() != new_change
.program()) {
1709 c
->change_program (old_change
, new_change
.program());
1712 if (old_change
->bank() != new_change
.bank()) {
1713 c
->change_bank (old_change
, new_change
.bank());
1716 _model
->apply_command (*trackview
.session(), c
);
1718 _patch_changes
.clear ();
1719 display_patch_changes ();
1722 /** Add a patch change to the region.
1723 * @param t Time in frames relative to region position
1724 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1725 * get_channel_for_add())
1728 MidiRegionView::add_patch_change (framecnt_t t
, Evoral::PatchChange
<Evoral::MusicalTime
> const & patch
)
1730 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("add patch change"));
1731 c
->add (MidiModel::PatchChangePtr (
1732 new Evoral::PatchChange
<Evoral::MusicalTime
> (
1733 frames_to_beats (t
+ midi_region()->start()), get_channel_for_add(), patch
.program(), patch
.bank()
1737 _model
->apply_command (*trackview
.session(), c
);
1739 _patch_changes
.clear ();
1740 display_patch_changes ();
1744 MidiRegionView::move_patch_change (CanvasPatchChange
& pc
, Evoral::MusicalTime t
)
1746 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("move patch change"));
1747 c
->change_time (pc
.patch (), t
);
1748 _model
->apply_command (*trackview
.session(), c
);
1750 _patch_changes
.clear ();
1751 display_patch_changes ();
1755 MidiRegionView::delete_patch_change (CanvasPatchChange
* pc
)
1757 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("delete patch change"));
1758 c
->remove (pc
->patch ());
1759 _model
->apply_command (*trackview
.session(), c
);
1761 _patch_changes
.clear ();
1762 display_patch_changes ();
1766 MidiRegionView::previous_patch (CanvasPatchChange
& patch
)
1768 if (patch
.patch()->program() < 127) {
1769 MIDI::Name::PatchPrimaryKey key
;
1770 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1771 key
.program_number
++;
1772 change_patch_change (patch
, key
);
1777 MidiRegionView::next_patch (CanvasPatchChange
& patch
)
1779 if (patch
.patch()->program() > 0) {
1780 MIDI::Name::PatchPrimaryKey key
;
1781 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1782 key
.program_number
--;
1783 change_patch_change (patch
, key
);
1788 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1790 if (_selection
.empty()) {
1794 if (_selection
.erase (cne
) > 0) {
1795 cerr
<< "Erased a CNE from selection\n";
1800 MidiRegionView::delete_selection()
1802 if (_selection
.empty()) {
1806 start_note_diff_command (_("delete selection"));
1808 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1809 if ((*i
)->selected()) {
1810 _note_diff_command
->remove((*i
)->note());
1820 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1822 start_note_diff_command (_("delete note"));
1823 _note_diff_command
->remove (n
);
1826 trackview
.editor().hide_verbose_canvas_cursor ();
1830 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1832 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1833 if ((*i
)->selected() && (*i
) != ev
) {
1834 (*i
)->set_selected(false);
1835 (*i
)->hide_velocity();
1843 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1845 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1848 Selection::iterator tmp
= i
;
1851 (*i
)->set_selected (false);
1852 _selection
.erase (i
);
1861 /* don't bother with removing this regionview from the editor selection,
1862 since we're about to add another note, and thus put/keep this
1863 regionview in the editor selection.
1866 if (!ev
->selected()) {
1867 add_to_selection (ev
);
1872 MidiRegionView::select_all_notes ()
1876 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1877 add_to_selection (*i
);
1882 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1884 uint8_t low_note
= 127;
1885 uint8_t high_note
= 0;
1886 MidiModel::Notes
& notes (_model
->notes());
1887 _optimization_iterator
= _events
.begin();
1893 if (extend
&& _selection
.empty()) {
1899 /* scan existing selection to get note range */
1901 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1902 if ((*i
)->note()->note() < low_note
) {
1903 low_note
= (*i
)->note()->note();
1905 if ((*i
)->note()->note() > high_note
) {
1906 high_note
= (*i
)->note()->note();
1910 low_note
= min (low_note
, notenum
);
1911 high_note
= max (high_note
, notenum
);
1914 no_sound_notes
= true;
1916 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1918 boost::shared_ptr
<NoteType
> note (*n
);
1919 CanvasNoteEvent
* cne
;
1920 bool select
= false;
1922 if (((1 << note
->channel()) & channel_mask
) != 0) {
1924 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1927 } else if (note
->note() == notenum
) {
1933 if ((cne
= find_canvas_note (note
)) != 0) {
1934 // extend is false because we've taken care of it,
1935 // since it extends by time range, not pitch.
1936 note_selected (cne
, add
, false);
1940 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1944 no_sound_notes
= false;
1948 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
1950 MidiModel::Notes
& notes (_model
->notes());
1951 _optimization_iterator
= _events
.begin();
1953 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1955 boost::shared_ptr
<NoteType
> note (*n
);
1956 CanvasNoteEvent
* cne
;
1958 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
1959 if ((cne
= find_canvas_note (note
)) != 0) {
1960 if (cne
->selected()) {
1961 note_deselected (cne
);
1963 note_selected (cne
, true, false);
1971 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
1974 clear_selection_except(ev
);
1979 if (!ev
->selected()) {
1980 add_to_selection (ev
);
1984 /* find end of latest note selected, select all between that and the start of "ev" */
1986 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
1987 Evoral::MusicalTime latest
= 0;
1989 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1990 if ((*i
)->note()->end_time() > latest
) {
1991 latest
= (*i
)->note()->end_time();
1993 if ((*i
)->note()->time() < earliest
) {
1994 earliest
= (*i
)->note()->time();
1998 if (ev
->note()->end_time() > latest
) {
1999 latest
= ev
->note()->end_time();
2002 if (ev
->note()->time() < earliest
) {
2003 earliest
= ev
->note()->time();
2006 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2008 /* find notes entirely within OR spanning the earliest..latest range */
2010 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
2011 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
2012 add_to_selection (*i
);
2016 /* if events were guaranteed to be time sorted, we could do this.
2017 but as of sept 10th 2009, they no longer are.
2020 if ((*i
)->note()->time() > latest
) {
2029 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
2031 remove_from_selection (ev
);
2035 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
2045 // TODO: Make this faster by storing the last updated selection rect, and only
2046 // adjusting things that are in the area that appears/disappeared.
2047 // We probably need a tree to be able to find events in O(log(n)) time.
2049 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2051 /* check if any corner of the note is inside the rect
2054 1) this is computing "touched by", not "contained by" the rect.
2055 2) this does not require that events be sorted in time.
2058 const double ix1
= (*i
)->x1();
2059 const double ix2
= (*i
)->x2();
2060 const double iy1
= (*i
)->y1();
2061 const double iy2
= (*i
)->y2();
2063 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2064 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
2065 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2066 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
2069 if (!(*i
)->selected()) {
2070 add_to_selection (*i
);
2072 } else if ((*i
)->selected()) {
2073 // Not inside rectangle
2074 remove_from_selection (*i
);
2080 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
2082 Selection::iterator i
= _selection
.find (ev
);
2084 if (i
!= _selection
.end()) {
2085 _selection
.erase (i
);
2088 ev
->set_selected (false);
2089 ev
->hide_velocity ();
2091 if (_selection
.empty()) {
2092 PublicEditor
& editor (trackview
.editor());
2093 editor
.get_selection().remove (this);
2098 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
2100 bool add_mrv_selection
= false;
2102 if (_selection
.empty()) {
2103 add_mrv_selection
= true;
2106 if (_selection
.insert (ev
).second
) {
2107 ev
->set_selected (true);
2108 play_midi_note ((ev
)->note());
2111 if (add_mrv_selection
) {
2112 PublicEditor
& editor (trackview
.editor());
2113 editor
.get_selection().add (this);
2118 MidiRegionView::move_selection(double dx
, double dy
, double cumulative_dy
)
2120 typedef vector
<boost::shared_ptr
<NoteType
> > PossibleChord
;
2121 PossibleChord to_play
;
2122 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2124 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2125 if ((*i
)->note()->time() < earliest
) {
2126 earliest
= (*i
)->note()->time();
2130 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2131 if (Evoral::musical_time_equal ((*i
)->note()->time(), earliest
)) {
2132 to_play
.push_back ((*i
)->note());
2134 (*i
)->move_event(dx
, dy
);
2137 if (dy
&& !_selection
.empty() && !no_sound_notes
&& trackview
.editor().sound_notes()) {
2139 if (to_play
.size() > 1) {
2141 PossibleChord shifted
;
2143 for (PossibleChord::iterator n
= to_play
.begin(); n
!= to_play
.end(); ++n
) {
2144 boost::shared_ptr
<NoteType
> moved_note (new NoteType (**n
));
2145 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2146 shifted
.push_back (moved_note
);
2149 play_midi_chord (shifted
);
2151 } else if (!to_play
.empty()) {
2153 boost::shared_ptr
<NoteType
> moved_note (new NoteType (*to_play
.front()));
2154 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2155 play_midi_note (moved_note
);
2161 MidiRegionView::note_dropped(CanvasNoteEvent
*, frameoffset_t dt
, int8_t dnote
)
2163 assert (!_selection
.empty());
2165 uint8_t lowest_note_in_selection
= 127;
2166 uint8_t highest_note_in_selection
= 0;
2167 uint8_t highest_note_difference
= 0;
2169 // find highest and lowest notes first
2171 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2172 uint8_t pitch
= (*i
)->note()->note();
2173 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
2174 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
2178 cerr << "dnote: " << (int) dnote << endl;
2179 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2180 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2181 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2182 << int(highest_note_in_selection) << endl;
2183 cerr << "selection size: " << _selection.size() << endl;
2184 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2187 // Make sure the note pitch does not exceed the MIDI standard range
2188 if (highest_note_in_selection
+ dnote
> 127) {
2189 highest_note_difference
= highest_note_in_selection
- 127;
2192 start_note_diff_command (_("move notes"));
2194 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
2196 Evoral::MusicalTime new_time
= frames_to_beats (beats_to_frames ((*i
)->note()->time()) + dt
);
2202 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2204 uint8_t original_pitch
= (*i
)->note()->note();
2205 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
2207 // keep notes in standard midi range
2208 clamp_to_0_127(new_pitch
);
2210 // keep original pitch if note is dragged outside valid midi range
2211 if ((original_pitch
!= 0 && new_pitch
== 0)
2212 || (original_pitch
!= 127 && new_pitch
== 127)) {
2213 new_pitch
= original_pitch
;
2216 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2217 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2219 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::NoteNumber
, new_pitch
);
2224 // care about notes being moved beyond the upper/lower bounds on the canvas
2225 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2226 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2227 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2232 MidiRegionView::snap_pixel_to_frame(double x
)
2234 PublicEditor
& editor
= trackview
.editor();
2235 // x is region relative, convert it to global absolute frames
2236 framepos_t frame
= editor
.pixel_to_frame(x
) + _region
->position();
2237 editor
.snap_to(frame
);
2238 return frame
- _region
->position(); // convert back to region relative
2241 /** Snap a frame offset within our region using the current snap settings.
2242 * @param x Frame offset from this region's position.
2243 * @return Snapped frame offset from this region's position.
2246 MidiRegionView::snap_frame_to_frame (frameoffset_t x
)
2248 PublicEditor
& editor
= trackview
.editor();
2250 /* x is region relative, convert it to global absolute frames */
2251 framepos_t
const session_frame
= x
+ _region
->position();
2253 /* try a snap in either direction */
2254 framepos_t frame
= session_frame
;
2255 editor
.snap_to (frame
, 0);
2257 /* if we went off the beginning of the region, snap forwards */
2258 if (frame
< _region
->position ()) {
2259 frame
= session_frame
;
2260 editor
.snap_to (frame
, 1);
2263 /* back to region relative */
2264 return frame
- _region
->position();
2268 MidiRegionView::snap_to_pixel(double x
)
2270 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2274 MidiRegionView::get_position_pixels()
2276 framepos_t region_frame
= get_position();
2277 return trackview
.editor().frame_to_pixel(region_frame
);
2281 MidiRegionView::get_end_position_pixels()
2283 framepos_t frame
= get_position() + get_duration ();
2284 return trackview
.editor().frame_to_pixel(frame
);
2288 MidiRegionView::beats_to_frames(double beats
) const
2290 return _time_converter
.to(beats
);
2294 MidiRegionView::frames_to_beats(framepos_t frames
) const
2296 return _time_converter
.from(frames
);
2300 MidiRegionView::begin_resizing (bool /*at_front*/)
2302 _resize_data
.clear();
2304 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2305 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2307 // only insert CanvasNotes into the map
2309 NoteResizeData
*resize_data
= new NoteResizeData();
2310 resize_data
->canvas_note
= note
;
2312 // create a new SimpleRect from the note which will be the resize preview
2313 SimpleRect
*resize_rect
= new SimpleRect(
2314 *_note_group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2316 // calculate the colors: get the color settings
2317 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2318 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2321 // make the resize preview notes more transparent and bright
2322 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2324 // calculate color based on note velocity
2325 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2326 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2330 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2331 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2333 resize_data
->resize_rect
= resize_rect
;
2334 _resize_data
.push_back(resize_data
);
2339 /** Update resizing notes while user drags.
2340 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2341 * @param at_front which end of the note (true == note on, false == note off)
2342 * @param delta_x change in mouse position since the start of the drag
2343 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2344 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2345 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2346 * as the \a primary note.
2349 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2351 bool cursor_set
= false;
2353 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2354 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2355 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2360 current_x
= canvas_note
->x1() + delta_x
;
2362 current_x
= primary
->x1() + delta_x
;
2366 current_x
= canvas_note
->x2() + delta_x
;
2368 current_x
= primary
->x2() + delta_x
;
2373 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2374 resize_rect
->property_x2() = canvas_note
->x2();
2376 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2377 resize_rect
->property_x1() = canvas_note
->x1();
2383 beats
= snap_pixel_to_frame (current_x
);
2384 beats
= frames_to_beats (beats
);
2389 if (beats
< canvas_note
->note()->end_time()) {
2390 len
= canvas_note
->note()->time() - beats
;
2391 len
+= canvas_note
->note()->length();
2396 if (beats
>= canvas_note
->note()->time()) {
2397 len
= beats
- canvas_note
->note()->time();
2404 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2405 trackview
.editor().show_verbose_canvas_cursor_with (buf
);
2414 /** Finish resizing notes when the user releases the mouse button.
2415 * Parameters the same as for \a update_resizing().
2418 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2420 start_note_diff_command (_("resize notes"));
2422 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2423 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2424 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2429 current_x
= canvas_note
->x1() + delta_x
;
2431 current_x
= primary
->x1() + delta_x
;
2435 current_x
= canvas_note
->x2() + delta_x
;
2437 current_x
= primary
->x2() + delta_x
;
2441 current_x
= snap_pixel_to_frame (current_x
);
2442 current_x
= frames_to_beats (current_x
);
2444 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2445 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::StartTime
, current_x
);
2447 double len
= canvas_note
->note()->time() - current_x
;
2448 len
+= canvas_note
->note()->length();
2451 /* XXX convert to beats */
2452 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2457 double len
= current_x
- canvas_note
->note()->time();
2460 /* XXX convert to beats */
2461 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2469 _resize_data
.clear();
2474 MidiRegionView::change_note_channel (CanvasNoteEvent
* event
, int8_t channel
)
2476 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Channel
, (uint8_t) channel
);
2480 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2482 uint8_t new_velocity
;
2485 new_velocity
= event
->note()->velocity() + velocity
;
2486 clamp_to_0_127(new_velocity
);
2488 new_velocity
= velocity
;
2491 event
->set_selected (event
->selected()); // change color
2493 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Velocity
, new_velocity
);
2497 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2502 new_note
= event
->note()->note() + note
;
2507 clamp_to_0_127 (new_note
);
2508 note_diff_add_change (event
, MidiModel::NoteDiffCommand::NoteNumber
, new_note
);
2512 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2514 bool change_start
= false;
2515 bool change_length
= false;
2516 Evoral::MusicalTime new_start
= 0;
2517 Evoral::MusicalTime new_length
= 0;
2519 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2521 front_delta: if positive - move the start of the note later in time (shortening it)
2522 if negative - move the start of the note earlier in time (lengthening it)
2524 end_delta: if positive - move the end of the note later in time (lengthening it)
2525 if negative - move the end of the note earlier in time (shortening it)
2529 if (front_delta
< 0) {
2531 if (event
->note()->time() < -front_delta
) {
2534 new_start
= event
->note()->time() + front_delta
; // moves earlier
2537 /* start moved toward zero, so move the end point out to where it used to be.
2538 Note that front_delta is negative, so this increases the length.
2541 new_length
= event
->note()->length() - front_delta
;
2542 change_start
= true;
2543 change_length
= true;
2547 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2549 if (new_pos
< event
->note()->end_time()) {
2550 new_start
= event
->note()->time() + front_delta
;
2551 /* start moved toward the end, so move the end point back to where it used to be */
2552 new_length
= event
->note()->length() - front_delta
;
2553 change_start
= true;
2554 change_length
= true;
2561 bool can_change
= true;
2562 if (end_delta
< 0) {
2563 if (event
->note()->length() < -end_delta
) {
2569 new_length
= event
->note()->length() + end_delta
;
2570 change_length
= true;
2575 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_start
);
2578 if (change_length
) {
2579 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, new_length
);
2584 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2586 Evoral::MusicalTime new_time
;
2590 if (event
->note()->time() < -delta
) {
2593 new_time
= event
->note()->time() + delta
;
2596 new_time
= event
->note()->time() + delta
;
2602 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2606 MidiRegionView::change_note_length (CanvasNoteEvent
* event
, Evoral::MusicalTime t
)
2608 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, t
);
2612 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2616 if (_selection
.empty()) {
2631 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2632 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2638 start_note_diff_command (_("change velocities"));
2640 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2641 Selection::iterator next
= i
;
2643 change_note_velocity (*i
, delta
, true);
2649 if (!_selection
.empty()) {
2651 snprintf (buf
, sizeof (buf
), "Vel %d",
2652 (int) (*_selection
.begin())->note()->velocity());
2653 trackview
.editor().show_verbose_canvas_cursor_with (buf
, 10, 10);
2659 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2661 if (_selection
.empty()) {
2678 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2680 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2684 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2691 start_note_diff_command (_("transpose"));
2693 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2694 Selection::iterator next
= i
;
2696 change_note_note (*i
, delta
, true);
2704 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, Evoral::MusicalTime delta
, bool start
, bool end
)
2710 /* grab the current grid distance */
2712 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2714 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2715 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2725 start_note_diff_command (_("change note lengths"));
2727 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2728 Selection::iterator next
= i
;
2731 /* note the negation of the delta for start */
2733 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2742 MidiRegionView::nudge_notes (bool forward
)
2744 if (_selection
.empty()) {
2748 /* pick a note as the point along the timeline to get the nudge distance.
2749 its not necessarily the earliest note, so we may want to pull the notes out
2750 into a vector and sort before using the first one.
2753 framepos_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2755 framepos_t distance
;
2757 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2759 /* grid is off - use nudge distance */
2761 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2767 framepos_t next_pos
= ref_point
;
2770 if (max_framepos
- 1 < next_pos
) {
2774 if (next_pos
== 0) {
2780 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2781 distance
= ref_point
- next_pos
;
2784 if (distance
== 0) {
2788 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2794 start_note_diff_command (_("nudge"));
2796 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2797 Selection::iterator next
= i
;
2799 change_note_time (*i
, delta
, true);
2807 MidiRegionView::change_channel(uint8_t channel
)
2809 start_note_diff_command(_("change channel"));
2810 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2811 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::Channel
, channel
);
2819 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2821 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2823 pre_enter_cursor
= editor
->get_canvas_cursor ();
2825 if (_mouse_state
== SelectTouchDragging
) {
2826 note_selected (ev
, true);
2829 show_verbose_canvas_cursor (ev
->note ());
2833 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2835 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2837 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2838 (*i
)->hide_velocity ();
2841 editor
->hide_verbose_canvas_cursor ();
2843 if (pre_enter_cursor
) {
2844 editor
->set_canvas_cursor (pre_enter_cursor
);
2845 pre_enter_cursor
= 0;
2850 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange
* ev
)
2853 s
<< ((int) ev
->patch()->program() + 1) << ":" << (ev
->patch()->bank() + 1);
2854 trackview
.editor().show_verbose_canvas_cursor_with (s
.str().c_str(), 10, 20);
2858 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange
*)
2860 trackview
.editor().hide_verbose_canvas_cursor ();
2864 MidiRegionView::note_mouse_position (float x_fraction
, float /*y_fraction*/, bool can_set_cursor
)
2866 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2868 if (x_fraction
> 0.0 && x_fraction
< 0.25) {
2869 editor
->set_canvas_cursor (editor
->cursors()->left_side_trim
);
2870 } else if (x_fraction
>= 0.75 && x_fraction
< 1.0) {
2871 editor
->set_canvas_cursor (editor
->cursors()->right_side_trim
);
2873 if (pre_enter_cursor
&& can_set_cursor
) {
2874 editor
->set_canvas_cursor (pre_enter_cursor
);
2880 MidiRegionView::set_frame_color()
2884 TimeAxisViewItem::set_frame_color ();
2891 f
= ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2892 } else if (high_enough_for_name
) {
2893 f
= ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2898 if (!rect_visible
) {
2899 f
= UINT_RGBA_CHANGE_A (f
, 0);
2902 frame
->property_fill_color_rgba() = f
;
2906 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2910 case FilterChannels
:
2911 _force_channel
= -1;
2914 _force_channel
= mask
;
2915 mask
= 0xFFFF; // Show all notes as active (below)
2918 // Update notes for selection
2919 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2920 (*i
)->on_channel_selection_change(mask
);
2923 _last_channel_selection
= mask
;
2925 _patch_changes
.clear ();
2926 display_patch_changes ();
2930 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2932 _model_name
= model
;
2933 _custom_device_mode
= custom_device_mode
;
2938 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
2940 if (_selection
.empty()) {
2944 PublicEditor
& editor (trackview
.editor());
2949 editor
.get_cut_buffer().add (selection_as_cut_buffer());
2957 start_note_diff_command();
2959 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2965 note_diff_remove_note (*i
);
2975 MidiRegionView::selection_as_cut_buffer () const
2979 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2980 NoteType
* n
= (*i
)->note().get();
2981 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
2984 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
2990 /** This method handles undo */
2992 MidiRegionView::paste (framepos_t pos
, float times
, const MidiCutBuffer
& mcb
)
2998 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("MIDI paste @ %1 times %2\n", pos
, times
));
3000 trackview
.session()->begin_reversible_command (_("paste"));
3002 start_note_diff_command (_("paste"));
3004 Evoral::MusicalTime beat_delta
;
3005 Evoral::MusicalTime paste_pos_beats
;
3006 Evoral::MusicalTime duration
;
3007 Evoral::MusicalTime end_point
= 0;
3009 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
3010 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
3011 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
3012 paste_pos_beats
= 0;
3014 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",
3015 (*mcb
.notes().begin())->time(),
3016 (*mcb
.notes().rbegin())->end_time(),
3017 duration
, pos
, _region
->position(),
3018 paste_pos_beats
, beat_delta
));
3022 for (int n
= 0; n
< (int) times
; ++n
) {
3024 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
3026 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
3027 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
3029 /* make all newly added notes selected */
3031 note_diff_add_note (copied_note
, true);
3032 end_point
= copied_note
->end_time();
3035 paste_pos_beats
+= duration
;
3038 /* if we pasted past the current end of the region, extend the region */
3040 framepos_t end_frame
= _region
->position() + beats_to_frames (end_point
);
3041 framepos_t region_end
= _region
->position() + _region
->length() - 1;
3043 if (end_frame
> region_end
) {
3045 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste extended region from %1 to %2\n", region_end
, end_frame
));
3047 _region
->clear_changes ();
3048 _region
->set_length (end_frame
, this);
3049 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
3054 trackview
.session()->commit_reversible_command ();
3057 struct EventNoteTimeEarlyFirstComparator
{
3058 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
3059 return a
->note()->time() < b
->note()->time();
3064 MidiRegionView::time_sort_events ()
3066 if (!_sort_needed
) {
3070 EventNoteTimeEarlyFirstComparator cmp
;
3073 _sort_needed
= false;
3077 MidiRegionView::goto_next_note ()
3079 // framepos_t pos = -1;
3080 bool use_next
= false;
3082 if (_events
.back()->selected()) {
3086 time_sort_events ();
3088 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3089 if ((*i
)->selected()) {
3092 } else if (use_next
) {
3094 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3099 /* use the first one */
3101 unique_select (_events
.front());
3106 MidiRegionView::goto_previous_note ()
3108 // framepos_t pos = -1;
3109 bool use_next
= false;
3111 if (_events
.front()->selected()) {
3115 time_sort_events ();
3117 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
3118 if ((*i
)->selected()) {
3121 } else if (use_next
) {
3123 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3128 /* use the last one */
3130 unique_select (*(_events
.rbegin()));
3134 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
3136 bool had_selected
= false;
3138 time_sort_events ();
3140 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3141 if ((*i
)->selected()) {
3142 selected
.insert ((*i
)->note());
3143 had_selected
= true;
3147 if (allow_all_if_none_selected
&& !had_selected
) {
3148 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3149 selected
.insert ((*i
)->note());
3155 MidiRegionView::update_ghost_note (double x
, double y
)
3160 _note_group
->w2i (x
, y
);
3161 framepos_t f
= trackview
.editor().pixel_to_frame (x
) + _region
->position ();
3162 trackview
.editor().snap_to (f
);
3163 f
-= _region
->position ();
3166 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
3172 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
3174 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
3175 _ghost_note
->note()->set_length (length
);
3176 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
3178 /* the ghost note does not appear in ghost regions, so pass false in here */
3179 update_note (_ghost_note
, false);
3181 show_verbose_canvas_cursor (_ghost_note
->note ());
3185 MidiRegionView::create_ghost_note (double x
, double y
)
3190 boost::shared_ptr
<NoteType
> g (new NoteType
);
3191 _ghost_note
= new NoEventCanvasNote (*this, *_note_group
, g
);
3192 _ghost_note
->property_outline_color_rgba() = 0x000000aa;
3193 update_ghost_note (x
, y
);
3194 _ghost_note
->show ();
3199 show_verbose_canvas_cursor (_ghost_note
->note ());
3203 MidiRegionView::snap_changed ()
3209 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
3213 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr
<NoteType
> n
) const
3216 snprintf (buf
, sizeof (buf
), "%s (%d) Chn %d\nVel %d",
3217 Evoral::midi_note_name (n
->note()).c_str(),
3219 (int) n
->channel() + 1,
3220 (int) n
->velocity());
3221 trackview
.editor().show_verbose_canvas_cursor_with (buf
, 10, 20);
3225 MidiRegionView::drop_down_keys ()
3227 _mouse_state
= None
;
3231 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double /*x*/, double y
)
3233 double note
= midi_stream_view()->y_to_note(y
);
3235 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3237 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
3239 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
3240 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3241 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
3242 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3247 bool add_mrv_selection
= false;
3249 if (_selection
.empty()) {
3250 add_mrv_selection
= true;
3253 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
3254 if (_selection
.insert (*i
).second
) {
3255 (*i
)->set_selected (true);
3259 if (add_mrv_selection
) {
3260 PublicEditor
& editor (trackview
.editor());
3261 editor
.get_selection().add (this);
3266 MidiRegionView::color_handler ()
3268 RegionView::color_handler ();
3270 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3271 (*i
)->set_selected ((*i
)->selected()); // will change color
3274 /* XXX probably more to do here */
3278 MidiRegionView::enable_display (bool yn
)
3280 RegionView::enable_display (yn
);
3287 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos
)
3289 if (_step_edit_cursor
== 0) {
3290 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
3292 _step_edit_cursor
= new ArdourCanvas::SimpleRect (*group
);
3293 _step_edit_cursor
->property_y1() = 0;
3294 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
3295 _step_edit_cursor
->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3296 _step_edit_cursor
->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3299 move_step_edit_cursor (pos
);
3300 _step_edit_cursor
->show ();
3304 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos
)
3306 _step_edit_cursor_position
= pos
;
3308 if (_step_edit_cursor
) {
3309 double pixel
= trackview
.editor().frame_to_pixel (beats_to_frames (pos
));
3310 _step_edit_cursor
->property_x1() = pixel
;
3311 set_step_edit_cursor_width (_step_edit_cursor_width
);
3316 MidiRegionView::hide_step_edit_cursor ()
3318 if (_step_edit_cursor
) {
3319 _step_edit_cursor
->hide ();
3324 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats
)
3326 _step_edit_cursor_width
= beats
;
3328 if (_step_edit_cursor
) {
3329 _step_edit_cursor
->property_x2() = _step_edit_cursor
->property_x1() + trackview
.editor().frame_to_pixel (beats_to_frames (beats
));
3333 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3334 * @param buf Data that has been recorded.
3335 * @param w Source that this data will end up in.
3338 MidiRegionView::data_recorded (boost::shared_ptr
<MidiBuffer
> buf
, boost::weak_ptr
<MidiSource
> w
)
3340 if (!_active_notes
) {
3341 /* we aren't actively being recorded to */
3345 boost::shared_ptr
<MidiSource
> src
= w
.lock ();
3346 if (!src
|| src
!= midi_region()->midi_source()) {
3347 /* recorded data was not destined for our source */
3351 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*> (&trackview
);
3352 BeatsFramesConverter
converter (trackview
.session()->tempo_map(), mtv
->midi_track()->get_capture_start_frame (0));
3354 framepos_t back
= max_framepos
;
3356 for (MidiBuffer::iterator i
= buf
->begin(); i
!= buf
->end(); ++i
) {
3357 Evoral::MIDIEvent
<MidiBuffer::TimeType
> const ev (*i
, false);
3358 assert (ev
.buffer ());
3360 Evoral::MusicalTime
const time_beats
= converter
.from (ev
.time () - converter
.origin_b ());
3362 if (ev
.type() == MIDI_CMD_NOTE_ON
) {
3364 boost::shared_ptr
<NoteType
> note (
3365 new NoteType (ev
.channel(), time_beats
, 0, ev
.note(), ev
.velocity())
3368 add_note (note
, true);
3370 /* fix up our note range */
3371 if (ev
.note() < _current_range_min
) {
3372 midi_stream_view()->apply_note_range (ev
.note(), _current_range_max
, true);
3373 } else if (ev
.note() > _current_range_max
) {
3374 midi_stream_view()->apply_note_range (_current_range_min
, ev
.note(), true);
3377 } else if (ev
.type() == MIDI_CMD_NOTE_OFF
) {
3378 resolve_note (ev
.note (), time_beats
);
3384 midi_stream_view()->check_record_layers (region(), back
);
3388 MidiRegionView::trim_front_starting ()
3390 /* Reparent the note group to the region view's parent, so that it doesn't change
3391 when the region view is trimmed.
3393 _temporary_note_group
= new ArdourCanvas::Group (*group
->property_parent ());
3394 _temporary_note_group
->move (group
->property_x(), group
->property_y());
3395 _note_group
->reparent (*_temporary_note_group
);
3399 MidiRegionView::trim_front_ending ()
3401 _note_group
->reparent (*group
);
3402 delete _temporary_note_group
;
3403 _temporary_note_group
= 0;
3405 if (_region
->start() < 0) {
3406 /* Trim drag made start time -ve; fix this */
3407 midi_region()->fix_negative_start ();
3411 /** @return channel (counted from 0) to add an event to, based on the current setting
3412 * of the channel selector.
3415 MidiRegionView::get_channel_for_add () const
3417 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3418 uint16_t const chn_mask
= mtv
->channel_selector().get_selected_channels();
3420 uint8_t channel
= 0;
3422 /* pick the highest selected channel, unless all channels are selected,
3423 which is interpreted to mean channel 1 (zero)
3426 for (uint16_t i
= 0; i
< 16; ++i
) {
3427 if (chn_mask
& (1<<i
)) {
3433 if (chn_cnt
== 16) {
3441 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange
* pc
)
3443 PatchChangeDialog
d (&_time_converter
, trackview
.session(), *pc
->patch (), Gtk::Stock::APPLY
);
3444 if (d
.run () != Gtk::RESPONSE_ACCEPT
) {
3448 change_patch_change (pc
->patch(), d
.patch ());