2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas_patch_change.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "note_player.h"
65 #include "public_editor.h"
66 #include "rgb_macros.h"
67 #include "selection.h"
68 #include "simpleline.h"
69 #include "streamview.h"
71 #include "mouse_cursors.h"
72 #include "patch_change_dialog.h"
73 #include "verbose_cursor.h"
77 using namespace ARDOUR
;
79 using namespace Editing
;
80 using namespace ArdourCanvas
;
81 using Gtkmm2ext::Keyboard
;
83 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
84 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
const & basic_color
)
85 : RegionView (parent
, tv
, r
, spu
, basic_color
)
87 , _last_channel_selection(0xFFFF)
88 , _current_range_min(0)
89 , _current_range_max(0)
90 , _model_name(string())
91 , _custom_device_mode(string())
93 , _note_group(new ArdourCanvas::Group(*group
))
94 , _note_diff_command (0)
97 , _step_edit_cursor (0)
98 , _step_edit_cursor_width (1.0)
99 , _step_edit_cursor_position (0.0)
100 , _channel_selection_scoped_note (0)
101 , _temporary_note_group (0)
104 , _sort_needed (true)
105 , _optimization_iterator (_events
.end())
107 , _no_sound_notes (false)
110 , _pre_enter_cursor (0)
112 _note_group
->raise_to_top();
113 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
115 connect_to_diskstream ();
118 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
119 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
& basic_color
,
120 TimeAxisViewItem::Visibility visibility
)
121 : RegionView (parent
, tv
, r
, spu
, basic_color
, false, visibility
)
123 , _last_channel_selection(0xFFFF)
124 , _model_name(string())
125 , _custom_device_mode(string())
127 , _note_group(new ArdourCanvas::Group(*parent
))
128 , _note_diff_command (0)
131 , _step_edit_cursor (0)
132 , _step_edit_cursor_width (1.0)
133 , _step_edit_cursor_position (0.0)
134 , _channel_selection_scoped_note (0)
135 , _temporary_note_group (0)
138 , _sort_needed (true)
139 , _optimization_iterator (_events
.end())
141 , _no_sound_notes (false)
145 _note_group
->raise_to_top();
146 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
148 connect_to_diskstream ();
151 MidiRegionView::MidiRegionView (const MidiRegionView
& other
)
152 : sigc::trackable(other
)
155 , _last_channel_selection(0xFFFF)
156 , _model_name(string())
157 , _custom_device_mode(string())
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
160 , _note_diff_command (0)
163 , _step_edit_cursor (0)
164 , _step_edit_cursor_width (1.0)
165 , _step_edit_cursor_position (0.0)
166 , _channel_selection_scoped_note (0)
167 , _temporary_note_group (0)
170 , _sort_needed (true)
171 , _optimization_iterator (_events
.end())
173 , _no_sound_notes (false)
180 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
181 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
186 MidiRegionView::MidiRegionView (const MidiRegionView
& other
, boost::shared_ptr
<MidiRegion
> region
)
187 : RegionView (other
, boost::shared_ptr
<Region
> (region
))
189 , _last_channel_selection(0xFFFF)
190 , _model_name(string())
191 , _custom_device_mode(string())
193 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
194 , _note_diff_command (0)
197 , _step_edit_cursor (0)
198 , _step_edit_cursor_width (1.0)
199 , _step_edit_cursor_position (0.0)
200 , _channel_selection_scoped_note (0)
201 , _temporary_note_group (0)
204 , _sort_needed (true)
205 , _optimization_iterator (_events
.end())
207 , _no_sound_notes (false)
214 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
215 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
221 MidiRegionView::init (Gdk::Color
const & basic_color
, bool wfd
)
223 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
225 CanvasNoteEvent::CanvasNoteEventDeleted
.connect (note_delete_connection
, MISSING_INVALIDATOR
,
226 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection
, this, _1
),
230 midi_region()->midi_source(0)->load_model();
233 _model
= midi_region()->midi_source(0)->model();
234 _enable_display
= false;
236 RegionView::init (basic_color
, false);
238 compute_colors (basic_color
);
240 set_height (trackview
.current_height());
243 region_sync_changed ();
244 region_resized (ARDOUR::bounds_change
);
247 reset_width_dependent_items (_pixel_width
);
251 _enable_display
= true;
254 display_model (_model
);
258 group
->raise_to_top();
259 group
->signal_event().connect(
260 sigc::mem_fun(this, &MidiRegionView::canvas_event
), false);
262 midi_view()->signal_channel_mode_changed().connect(
263 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed
));
265 midi_view()->signal_midi_patch_settings_changed().connect(
266 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed
));
268 trackview
.editor().SnapChanged
.connect(snap_changed_connection
, invalidator(*this),
269 ui_bind(&MidiRegionView::snap_changed
, this),
272 connect_to_diskstream ();
276 MidiRegionView::connect_to_diskstream ()
278 midi_view()->midi_track()->DataRecorded
.connect(
279 *this, invalidator(*this),
280 ui_bind(&MidiRegionView::data_recorded
, this, _1
, _2
),
285 MidiRegionView::canvas_event(GdkEvent
* ev
)
288 case GDK_ENTER_NOTIFY
:
289 case GDK_LEAVE_NOTIFY
:
290 _last_event_x
= ev
->crossing
.x
;
291 _last_event_y
= ev
->crossing
.y
;
293 case GDK_MOTION_NOTIFY
:
294 _last_event_x
= ev
->motion
.x
;
295 _last_event_y
= ev
->motion
.y
;
301 if (!trackview
.editor().internal_editing()) {
307 return scroll (&ev
->scroll
);
310 return key_press (&ev
->key
);
312 case GDK_KEY_RELEASE
:
313 return key_release (&ev
->key
);
315 case GDK_BUTTON_PRESS
:
316 return button_press (&ev
->button
);
318 case GDK_2BUTTON_PRESS
:
321 case GDK_BUTTON_RELEASE
:
322 return button_release (&ev
->button
);
324 case GDK_ENTER_NOTIFY
:
325 return enter_notify (&ev
->crossing
);
327 case GDK_LEAVE_NOTIFY
:
328 return leave_notify (&ev
->crossing
);
330 case GDK_MOTION_NOTIFY
:
331 return motion (&ev
->motion
);
341 MidiRegionView::remove_ghost_note ()
348 MidiRegionView::enter_notify (GdkEventCrossing
* ev
)
350 trackview
.editor().MouseModeChanged
.connect (
351 _mouse_mode_connection
, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed
, this), gui_context ()
354 Keyboard::magic_widget_grab_focus();
357 if (trackview
.editor().current_mouse_mode() == MouseRange
) {
358 create_ghost_note (ev
->x
, ev
->y
);
365 MidiRegionView::leave_notify (GdkEventCrossing
*)
367 _mouse_mode_connection
.disconnect ();
369 trackview
.editor().verbose_cursor()->hide ();
370 remove_ghost_note ();
375 MidiRegionView::mouse_mode_changed ()
377 if (trackview
.editor().current_mouse_mode() == MouseRange
&& trackview
.editor().internal_editing()) {
378 create_ghost_note (_last_event_x
, _last_event_y
);
380 remove_ghost_note ();
381 trackview
.editor().verbose_cursor()->hide ();
386 MidiRegionView::button_press (GdkEventButton
* ev
)
388 if (ev
->button
!= 1) {
395 group
->w2i (_last_x
, _last_y
);
397 if (_mouse_state
!= SelectTouchDragging
) {
399 _pressed_button
= ev
->button
;
400 _mouse_state
= Pressed
;
405 _pressed_button
= ev
->button
;
411 MidiRegionView::button_release (GdkEventButton
* ev
)
413 double event_x
, event_y
;
415 if (ev
->button
!= 1) {
422 group
->w2i(event_x
, event_y
);
423 group
->ungrab(ev
->time
);
425 switch (_mouse_state
) {
426 case Pressed
: // Clicked
428 switch (trackview
.editor().current_mouse_mode()) {
434 if (Keyboard::is_insert_note_event(ev
)) {
436 double event_x
, event_y
;
440 group
->w2i(event_x
, event_y
);
443 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
449 create_note_at (event_x
, event_y
, beats
, true);
457 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
463 create_note_at (event_x
, event_y
, beats
, true);
474 case SelectRectDragging
: // Select drag done
481 case AddDragging
: // Add drag done
485 if (Keyboard::is_insert_note_event(ev
) || trackview
.editor().current_mouse_mode() == MouseRange
) {
487 if (_drag_rect
->property_x2() > _drag_rect
->property_x1() + 2) {
489 const double x
= _drag_rect
->property_x1();
490 const double length
= trackview
.editor().pixel_to_frame (_drag_rect
->property_x2() - _drag_rect
->property_x1());
492 create_note_at (x
, _drag_rect
->property_y1(), frames_to_beats(length
), true);
499 create_ghost_note (ev
->x
, ev
->y
);
509 MidiRegionView::motion (GdkEventMotion
* ev
)
511 double event_x
, event_y
;
512 framepos_t event_frame
= 0;
516 group
->w2i(event_x
, event_y
);
518 // convert event_x to global frame
519 event_frame
= snap_pixel_to_frame (event_x
);
521 if (!_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
522 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())
523 && _mouse_state
!= AddDragging
) {
525 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
);
530 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
) {
535 trackview
.editor().verbose_cursor()->hide ();
536 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() == MouseRange
) {
537 update_ghost_note (ev
->x
, ev
->y
);
540 /* any motion immediately hides velocity text that may have been visible */
542 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
543 (*i
)->hide_velocity ();
546 switch (_mouse_state
) {
547 case Pressed
: // Maybe start a drag, if we've moved a bit
549 if (fabs (event_x
- _last_x
) < 1 && fabs (event_y
- _last_y
) < 1) {
550 /* 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())) {
558 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
559 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
563 _drag_start_x
= event_x
;
564 _drag_start_y
= event_y
;
566 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
567 _drag_rect
->property_x1() = event_x
;
568 _drag_rect
->property_y1() = event_y
;
569 _drag_rect
->property_x2() = event_x
;
570 _drag_rect
->property_y2() = event_y
;
571 _drag_rect
->property_outline_what() = 0xFF;
572 _drag_rect
->property_outline_color_rgba()
573 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
574 _drag_rect
->property_fill_color_rgba()
575 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
577 _mouse_state
= SelectRectDragging
;
580 } else if (trackview
.editor().internal_editing()) {
581 // Add note drag start
586 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
587 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
591 _drag_start_x
= event_x
;
592 _drag_start_y
= event_y
;
594 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
595 _drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
597 _drag_rect
->property_y1() = midi_stream_view()->note_to_y(
598 midi_stream_view()->y_to_note(event_y
));
599 _drag_rect
->property_x2() = trackview
.editor().frame_to_pixel(event_frame
);
600 _drag_rect
->property_y2() = _drag_rect
->property_y1()
601 + floor(midi_stream_view()->note_height());
602 _drag_rect
->property_outline_what() = 0xFF;
603 _drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
604 _drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
606 _mouse_state
= AddDragging
;
613 trackview
.editor().verbose_cursor()->hide ();
621 case SelectRectDragging
: // Select drag motion
622 case AddDragging
: // Add note drag motion
627 GdkModifierType state
;
628 gdk_window_get_pointer(ev
->window
, &t_x
, &t_y
, &state
);
633 if (_mouse_state
== AddDragging
) {
634 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
639 if (event_x
> _drag_start_x
) {
640 _drag_rect
->property_x2() = event_x
;
643 _drag_rect
->property_x1() = event_x
;
647 if (_drag_rect
&& _mouse_state
== SelectRectDragging
) {
649 if (event_y
> _drag_start_y
) {
650 _drag_rect
->property_y2() = event_y
;
653 _drag_rect
->property_y1() = event_y
;
656 update_drag_selection(_drag_start_x
, event_x
, _drag_start_y
, event_y
);
662 case SelectTouchDragging
:
674 MidiRegionView::scroll (GdkEventScroll
* ev
)
676 if (_selection
.empty()) {
680 trackview
.editor().verbose_cursor()->hide ();
682 bool fine
= !Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
);
684 if (ev
->direction
== GDK_SCROLL_UP
) {
685 change_velocities (true, fine
, false);
686 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
687 change_velocities (false, fine
, false);
693 MidiRegionView::key_press (GdkEventKey
* ev
)
695 /* since GTK bindings are generally activated on press, and since
696 detectable auto-repeat is the name of the game and only sends
697 repeated presses, carry out key actions at key press, not release.
700 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
701 _mouse_state
= SelectTouchDragging
;
704 } else if (ev
->keyval
== GDK_Escape
) {
708 } else if (ev
->keyval
== GDK_comma
|| ev
->keyval
== GDK_period
) {
710 bool start
= (ev
->keyval
== GDK_comma
);
711 bool end
= (ev
->keyval
== GDK_period
);
712 bool shorter
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
);
713 bool fine
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
715 change_note_lengths (fine
, shorter
, 0.0, start
, end
);
719 } else if (ev
->keyval
== GDK_Delete
) {
724 } else if (ev
->keyval
== GDK_Tab
) {
726 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
727 goto_previous_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
729 goto_next_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
733 } else if (ev
->keyval
== GDK_ISO_Left_Tab
) {
735 /* Shift-TAB generates ISO Left Tab, for some reason */
737 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
738 goto_previous_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
740 goto_next_note (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
));
746 } else if (ev
->keyval
== GDK_Up
) {
748 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
749 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
751 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
752 change_velocities (true, fine
, allow_smush
);
754 transpose (true, fine
, allow_smush
);
758 } else if (ev
->keyval
== GDK_Down
) {
760 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
761 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
763 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
764 change_velocities (false, fine
, allow_smush
);
766 transpose (false, fine
, allow_smush
);
770 } else if (ev
->keyval
== GDK_Left
) {
775 } else if (ev
->keyval
== GDK_Right
) {
780 } else if (ev
->keyval
== GDK_Control_L
) {
783 } else if (ev
->keyval
== GDK_c
) {
792 MidiRegionView::key_release (GdkEventKey
* ev
)
794 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
802 MidiRegionView::channel_edit ()
805 uint8_t current_channel
;
807 if (_selection
.empty()) {
811 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
813 current_channel
= (*i
)->note()->channel ();
818 MidiChannelDialog
channel_dialog (current_channel
);
819 int ret
= channel_dialog
.run ();
822 case Gtk::RESPONSE_OK
:
828 uint8_t new_channel
= channel_dialog
.active_channel ();
830 start_note_diff_command (_("channel edit"));
832 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
833 Selection::iterator next
= i
;
835 change_note_channel (*i
, new_channel
);
843 MidiRegionView::show_list_editor ()
846 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
848 _list_editor
->present ();
851 /** Add a note to the model, and the view, at a canvas (click) coordinate.
852 * \param x horizontal position in pixels
853 * \param y vertical position in pixels
854 * \param length duration of the note in beats, which will be snapped to the grid
855 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
858 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
860 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
861 MidiStreamView
* const view
= mtv
->midi_view();
863 double note
= view
->y_to_note(y
);
866 assert(note
<= 127.0);
868 // Start of note in frames relative to region start
869 framepos_t
const start_frames
= snap_pixel_to_frame (x
);
870 assert(start_frames
>= 0);
873 length
= frames_to_beats(
874 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
876 assert (length
!= 0);
879 length
= frames_to_beats (beats_to_frames (length
) - 1);
882 const boost::shared_ptr
<NoteType
> new_note (new NoteType (mtv
->get_channel_for_add (),
883 frames_to_beats(start_frames
+ _region
->start()), length
,
884 (uint8_t)note
, 0x40));
886 if (_model
->contains (new_note
)) {
890 view
->update_note_range(new_note
->note());
892 MidiModel::NoteDiffCommand
* cmd
= _model
->new_note_diff_command("add note");
894 _model
->apply_command(*trackview
.session(), cmd
);
896 play_midi_note (new_note
);
900 MidiRegionView::clear_events()
905 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
906 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
911 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
916 _patch_changes
.clear();
918 _optimization_iterator
= _events
.end();
922 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
926 content_connection
.disconnect ();
927 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
931 if (_enable_display
) {
937 MidiRegionView::start_note_diff_command (string name
)
939 if (!_note_diff_command
) {
940 _note_diff_command
= _model
->new_note_diff_command (name
);
945 MidiRegionView::note_diff_add_note (const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
947 if (_note_diff_command
) {
948 _note_diff_command
->add (note
);
951 _marked_for_selection
.insert(note
);
954 _marked_for_velocity
.insert(note
);
959 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent
* ev
)
961 if (_note_diff_command
&& ev
->note()) {
962 _note_diff_command
->remove(ev
->note());
967 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
968 MidiModel::NoteDiffCommand::Property property
,
971 if (_note_diff_command
) {
972 _note_diff_command
->change (ev
->note(), property
, val
);
977 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
978 MidiModel::NoteDiffCommand::Property property
,
979 Evoral::MusicalTime val
)
981 if (_note_diff_command
) {
982 _note_diff_command
->change (ev
->note(), property
, val
);
987 MidiRegionView::apply_diff (bool as_subcommand
)
991 if (!_note_diff_command
) {
995 if ((add_or_remove
= _note_diff_command
->adds_or_removes())) {
996 // Mark all selected notes for selection when model reloads
997 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
998 _marked_for_selection
.insert((*i
)->note());
1002 if (as_subcommand
) {
1003 _model
->apply_command_as_subcommand (*trackview
.session(), _note_diff_command
);
1005 _model
->apply_command (*trackview
.session(), _note_diff_command
);
1008 _note_diff_command
= 0;
1009 midi_view()->midi_track()->playlist_modified();
1011 if (add_or_remove
) {
1012 _marked_for_selection
.clear();
1015 _marked_for_velocity
.clear();
1019 MidiRegionView::abort_command()
1021 delete _note_diff_command
;
1022 _note_diff_command
= 0;
1027 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
1029 if (_optimization_iterator
!= _events
.end()) {
1030 ++_optimization_iterator
;
1033 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
1034 return *_optimization_iterator
;
1037 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
1038 if ((*_optimization_iterator
)->note() == note
) {
1039 return *_optimization_iterator
;
1047 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
1049 MidiModel::Notes notes
;
1050 _model
->get_notes (notes
, op
, val
, chan_mask
);
1052 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1053 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
1061 MidiRegionView::redisplay_model()
1063 // Don't redisplay the model if we're currently recording and displaying that
1064 if (_active_notes
) {
1072 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1073 (*i
)->invalidate ();
1076 MidiModel::ReadLock
lock(_model
->read_lock());
1078 MidiModel::Notes
& notes (_model
->notes());
1079 _optimization_iterator
= _events
.begin();
1081 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1083 boost::shared_ptr
<NoteType
> note (*n
);
1084 CanvasNoteEvent
* cne
;
1087 if (note_in_region_range (note
, visible
)) {
1089 if ((cne
= find_canvas_note (note
)) != 0) {
1096 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
1098 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
1110 add_note (note
, visible
);
1115 if ((cne
= find_canvas_note (note
)) != 0) {
1123 /* remove note items that are no longer valid */
1125 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
1126 if (!(*i
)->valid ()) {
1128 i
= _events
.erase (i
);
1134 _patch_changes
.clear();
1138 display_patch_changes ();
1140 _marked_for_selection
.clear ();
1141 _marked_for_velocity
.clear ();
1143 /* we may have caused _events to contain things out of order (e.g. if a note
1144 moved earlier or later). we don't generally need them in time order, but
1145 make a note that a sort is required for those cases that require it.
1148 _sort_needed
= true;
1152 MidiRegionView::display_patch_changes ()
1154 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1155 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
1157 for (uint8_t i
= 0; i
< 16; ++i
) {
1158 if (chn_mask
& (1<<i
)) {
1159 display_patch_changes_on_channel (i
);
1165 MidiRegionView::display_patch_changes_on_channel (uint8_t channel
)
1167 for (MidiModel::PatchChanges::const_iterator i
= _model
->patch_changes().begin(); i
!= _model
->patch_changes().end(); ++i
) {
1169 if ((*i
)->channel() != channel
) {
1173 MIDI::Name::PatchPrimaryKey
patch_key ((*i
)->bank_msb (), (*i
)->bank_lsb (), (*i
)->program ());
1175 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1176 MIDI::Name::MidiPatchManager::instance().find_patch(
1177 _model_name
, _custom_device_mode
, channel
, patch_key
);
1180 add_canvas_patch_change (*i
, patch
->name());
1183 /* program and bank numbers are zero-based: convert to one-based */
1184 snprintf (buf
, 16, "%d\n%d", (*i
)->program() + 1, (*i
)->bank() + 1);
1185 add_canvas_patch_change (*i
, buf
);
1191 MidiRegionView::display_sysexes()
1193 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1194 Evoral::MusicalTime time
= (*i
)->time();
1199 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1200 str
<< int((*i
)->buffer()[b
]);
1201 if (b
!= (*i
)->size() -1) {
1205 string text
= str
.str();
1207 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1209 double height
= midi_stream_view()->contents_height();
1211 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1212 new CanvasSysEx(*this, *_note_group
, text
, height
, x
, 1.0));
1214 // Show unless patch change is beyond the region bounds
1215 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1221 _sys_exes
.push_back(sysex
);
1226 MidiRegionView::~MidiRegionView ()
1228 in_destructor
= true;
1230 trackview
.editor().verbose_cursor()->hide ();
1232 note_delete_connection
.disconnect ();
1234 delete _list_editor
;
1236 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1238 if (_active_notes
) {
1246 delete _note_diff_command
;
1247 delete _step_edit_cursor
;
1248 delete _temporary_note_group
;
1252 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1254 RegionView::region_resized(what_changed
);
1256 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1257 set_duration(_region
->length(), 0);
1258 if (_enable_display
) {
1265 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1267 RegionView::reset_width_dependent_items(pixel_width
);
1268 assert(_pixel_width
== pixel_width
);
1270 if (_enable_display
) {
1274 move_step_edit_cursor (_step_edit_cursor_position
);
1275 set_step_edit_cursor_width (_step_edit_cursor_width
);
1279 MidiRegionView::set_height (double height
)
1281 static const double FUDGE
= 2.0;
1282 const double old_height
= _height
;
1283 RegionView::set_height(height
);
1284 _height
= height
- FUDGE
;
1286 apply_note_range(midi_stream_view()->lowest_note(),
1287 midi_stream_view()->highest_note(),
1288 height
!= old_height
+ FUDGE
);
1291 name_pixbuf
->raise_to_top();
1294 for (PatchChanges::iterator x
= _patch_changes
.begin(); x
!= _patch_changes
.end(); ++x
) {
1295 (*x
)->set_height (midi_stream_view()->contents_height());
1298 if (_step_edit_cursor
) {
1299 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
1304 /** Apply the current note range from the stream view
1305 * by repositioning/hiding notes as necessary
1308 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1310 if (!_enable_display
) {
1314 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1318 _current_range_min
= min
;
1319 _current_range_max
= max
;
1321 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1322 CanvasNoteEvent
* event
= *i
;
1323 boost::shared_ptr
<NoteType
> note (event
->note());
1325 if (note
->note() < _current_range_min
||
1326 note
->note() > _current_range_max
) {
1332 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1334 const double y1
= midi_stream_view()->note_to_y(note
->note());
1335 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1337 cnote
->property_y1() = y1
;
1338 cnote
->property_y2() = y2
;
1340 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1342 const double diamond_size
= update_hit (chit
);
1344 chit
->set_height (diamond_size
);
1350 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1354 double unit_position
= _region
->position () / samples_per_unit
;
1355 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1356 MidiGhostRegion
* ghost
;
1358 if (mtv
&& mtv
->midi_view()) {
1359 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1360 to allow having midi notes on top of note lines and waveforms.
1362 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1364 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1367 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1368 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1369 ghost
->add_note(note
);
1373 ghost
->set_height ();
1374 ghost
->set_duration (_region
->length() / samples_per_unit
);
1375 ghosts
.push_back (ghost
);
1377 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1383 /** Begin tracking note state for successive calls to add_event
1386 MidiRegionView::begin_write()
1388 assert(!_active_notes
);
1389 _active_notes
= new CanvasNote
*[128];
1390 for (unsigned i
=0; i
< 128; ++i
) {
1391 _active_notes
[i
] = 0;
1396 /** Destroy note state for add_event
1399 MidiRegionView::end_write()
1401 delete[] _active_notes
;
1403 _marked_for_selection
.clear();
1404 _marked_for_velocity
.clear();
1408 /** Resolve an active MIDI note (while recording).
1411 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1413 if (midi_view()->note_mode() != Sustained
) {
1417 if (_active_notes
&& _active_notes
[note
]) {
1419 const framepos_t end_time_frames
= beats_to_frames(end_time
);
1421 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1422 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1423 _active_notes
[note
] = 0;
1428 /** Extend active notes to rightmost edge of region (if length is changed)
1431 MidiRegionView::extend_active_notes()
1433 if (!_active_notes
) {
1437 for (unsigned i
=0; i
< 128; ++i
) {
1438 if (_active_notes
[i
]) {
1439 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1446 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1448 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1452 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1454 if (!route_ui
|| !route_ui
->midi_track()) {
1458 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1464 MidiRegionView::play_midi_chord (vector
<boost::shared_ptr
<NoteType
> > notes
)
1466 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1470 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1472 if (!route_ui
|| !route_ui
->midi_track()) {
1476 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1478 for (vector
<boost::shared_ptr
<NoteType
> >::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1487 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1489 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1491 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1492 (note_start_frames
< _region
->start());
1494 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1495 (note
->note() <= midi_stream_view()->highest_note());
1500 /** Update a canvas note's size from its model note.
1501 * @param ev Canvas note to update.
1502 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1505 MidiRegionView::update_note (CanvasNote
* ev
, bool update_ghost_regions
)
1507 boost::shared_ptr
<NoteType
> note
= ev
->note();
1509 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1511 /* trim note display to not overlap the end of its region */
1512 const framepos_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1514 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1515 const double y1
= midi_stream_view()->note_to_y(note
->note());
1516 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1518 ev
->property_x1() = x
;
1519 ev
->property_y1() = y1
;
1521 if (note
->length() > 0) {
1522 ev
->property_x2() = note_endpixel
;
1524 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1527 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1529 if (note
->length() == 0) {
1530 if (_active_notes
) {
1531 assert(note
->note() < 128);
1532 // If this note is already active there's a stuck note,
1533 // finish the old note rectangle
1534 if (_active_notes
[note
->note()]) {
1535 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1536 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1537 old_rect
->property_x2() = x
;
1538 old_rect
->property_outline_what() = (guint32
) 0xF;
1540 _active_notes
[note
->note()] = ev
;
1542 /* outline all but right edge */
1543 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1545 /* outline all edges */
1546 ev
->property_outline_what() = (guint32
) 0xF;
1549 if (update_ghost_regions
) {
1550 for (std::vector
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
1551 MidiGhostRegion
* gr
= dynamic_cast<MidiGhostRegion
*> (*i
);
1553 gr
->update_note (ev
);
1560 MidiRegionView::update_hit (CanvasHit
* ev
)
1562 boost::shared_ptr
<NoteType
> note
= ev
->note();
1564 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1565 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1566 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1567 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1571 return diamond_size
;
1574 /** Add a MIDI note to the view (with length).
1576 * If in sustained mode, notes with length 0 will be considered active
1577 * notes, and resolve_note should be called when the corresponding note off
1578 * event arrives, to properly display the note.
1581 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1583 CanvasNoteEvent
* event
= 0;
1585 assert(note
->time() >= 0);
1586 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1588 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1590 if (midi_view()->note_mode() == Sustained
) {
1592 CanvasNote
* ev_rect
= new CanvasNote(*this, *_note_group
, note
);
1594 update_note (ev_rect
);
1598 MidiGhostRegion
* gr
;
1600 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1601 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1602 gr
->add_note(ev_rect
);
1606 } else if (midi_view()->note_mode() == Percussive
) {
1608 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1610 CanvasHit
* ev_diamond
= new CanvasHit(*this, *_note_group
, diamond_size
, note
);
1612 update_hit (ev_diamond
);
1621 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1622 note_selected(event
, true);
1625 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1626 event
->show_velocity();
1629 event
->on_channel_selection_change(_last_channel_selection
);
1630 _events
.push_back(event
);
1639 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1640 MidiStreamView
* const view
= mtv
->midi_view();
1642 view
->update_note_range(note
->note());
1646 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1647 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1649 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1651 /* potentially extend region to hold new note */
1653 framepos_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1654 framepos_t region_end
= _region
->position() + _region
->length() - 1;
1656 if (end_frame
> region_end
) {
1657 _region
->set_length (end_frame
- _region
->position());
1660 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1661 MidiStreamView
* const view
= mtv
->midi_view();
1663 view
->update_note_range(new_note
->note());
1665 _marked_for_selection
.clear ();
1668 start_note_diff_command (_("step add"));
1669 note_diff_add_note (new_note
, true, false);
1672 // last_step_edit_note = new_note;
1676 MidiRegionView::step_sustain (Evoral::MusicalTime beats
)
1678 change_note_lengths (false, false, beats
, false, true);
1682 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch
, const string
& displaytext
)
1684 assert (patch
->time() >= 0);
1686 const double x
= trackview
.editor().frame_to_pixel (beats_to_frames (patch
->time()));
1688 double const height
= midi_stream_view()->contents_height();
1690 boost::shared_ptr
<CanvasPatchChange
> patch_change
= boost::shared_ptr
<CanvasPatchChange
>(
1691 new CanvasPatchChange(*this, *_note_group
,
1696 _custom_device_mode
,
1700 // Show unless patch change is beyond the region bounds
1701 if (patch
->time() - _region
->start() >= _region
->length() || patch
->time() < _region
->start()) {
1702 patch_change
->hide();
1704 patch_change
->show();
1707 _patch_changes
.push_back (patch_change
);
1711 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1713 MidiModel::PatchChanges::iterator i
= _model
->patch_change_lower_bound (time
);
1714 while (i
!= _model
->patch_changes().end() && (*i
)->channel() != channel
) {
1718 if (i
!= _model
->patch_changes().end()) {
1719 key
.msb
= (*i
)->bank_msb ();
1720 key
.lsb
= (*i
)->bank_lsb ();
1721 key
.program_number
= (*i
)->program ();
1723 key
.msb
= key
.lsb
= key
.program_number
= 0;
1726 assert (key
.is_sane());
1731 MidiRegionView::change_patch_change (CanvasPatchChange
& pc
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1733 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1735 if (pc
.patch()->program() != new_patch
.program_number
) {
1736 c
->change_program (pc
.patch (), new_patch
.program_number
);
1739 int const new_bank
= (new_patch
.msb
<< 7) | new_patch
.lsb
;
1740 if (pc
.patch()->bank() != new_bank
) {
1741 c
->change_bank (pc
.patch (), new_bank
);
1744 _model
->apply_command (*trackview
.session(), c
);
1746 _patch_changes
.clear ();
1747 display_patch_changes ();
1751 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change
, const Evoral::PatchChange
<Evoral::MusicalTime
> & new_change
)
1753 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1755 if (old_change
->time() != new_change
.time()) {
1756 c
->change_time (old_change
, new_change
.time());
1759 if (old_change
->channel() != new_change
.channel()) {
1760 c
->change_channel (old_change
, new_change
.channel());
1763 if (old_change
->program() != new_change
.program()) {
1764 c
->change_program (old_change
, new_change
.program());
1767 if (old_change
->bank() != new_change
.bank()) {
1768 c
->change_bank (old_change
, new_change
.bank());
1771 _model
->apply_command (*trackview
.session(), c
);
1773 _patch_changes
.clear ();
1774 display_patch_changes ();
1777 /** Add a patch change to the region.
1778 * @param t Time in frames relative to region position
1779 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1780 * MidiTimeAxisView::get_channel_for_add())
1783 MidiRegionView::add_patch_change (framecnt_t t
, Evoral::PatchChange
<Evoral::MusicalTime
> const & patch
)
1785 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1787 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("add patch change"));
1788 c
->add (MidiModel::PatchChangePtr (
1789 new Evoral::PatchChange
<Evoral::MusicalTime
> (
1790 frames_to_beats (t
+ midi_region()->start()), mtv
->get_channel_for_add(), patch
.program(), patch
.bank()
1794 _model
->apply_command (*trackview
.session(), c
);
1796 _patch_changes
.clear ();
1797 display_patch_changes ();
1801 MidiRegionView::move_patch_change (CanvasPatchChange
& pc
, Evoral::MusicalTime t
)
1803 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("move patch change"));
1804 c
->change_time (pc
.patch (), t
);
1805 _model
->apply_command (*trackview
.session(), c
);
1807 _patch_changes
.clear ();
1808 display_patch_changes ();
1812 MidiRegionView::delete_patch_change (CanvasPatchChange
* pc
)
1814 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("delete patch change"));
1815 c
->remove (pc
->patch ());
1816 _model
->apply_command (*trackview
.session(), c
);
1818 _patch_changes
.clear ();
1819 display_patch_changes ();
1823 MidiRegionView::previous_patch (CanvasPatchChange
& patch
)
1825 if (patch
.patch()->program() < 127) {
1826 MIDI::Name::PatchPrimaryKey key
;
1827 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1828 key
.program_number
++;
1829 change_patch_change (patch
, key
);
1834 MidiRegionView::next_patch (CanvasPatchChange
& patch
)
1836 if (patch
.patch()->program() > 0) {
1837 MIDI::Name::PatchPrimaryKey key
;
1838 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1839 key
.program_number
--;
1840 change_patch_change (patch
, key
);
1845 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1847 if (_selection
.empty()) {
1851 _selection
.erase (cne
);
1855 MidiRegionView::delete_selection()
1857 if (_selection
.empty()) {
1861 start_note_diff_command (_("delete selection"));
1863 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1864 if ((*i
)->selected()) {
1865 _note_diff_command
->remove((*i
)->note());
1875 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1877 start_note_diff_command (_("delete note"));
1878 _note_diff_command
->remove (n
);
1881 trackview
.editor().verbose_cursor()->hide ();
1885 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1887 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1888 if ((*i
)->selected() && (*i
) != ev
) {
1889 (*i
)->set_selected(false);
1890 (*i
)->hide_velocity();
1898 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1900 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1903 Selection::iterator tmp
= i
;
1906 (*i
)->set_selected (false);
1907 _selection
.erase (i
);
1916 /* don't bother with removing this regionview from the editor selection,
1917 since we're about to add another note, and thus put/keep this
1918 regionview in the editor selection.
1921 if (!ev
->selected()) {
1922 add_to_selection (ev
);
1927 MidiRegionView::select_all_notes ()
1931 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1932 add_to_selection (*i
);
1937 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1939 uint8_t low_note
= 127;
1940 uint8_t high_note
= 0;
1941 MidiModel::Notes
& notes (_model
->notes());
1942 _optimization_iterator
= _events
.begin();
1948 if (extend
&& _selection
.empty()) {
1954 /* scan existing selection to get note range */
1956 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1957 if ((*i
)->note()->note() < low_note
) {
1958 low_note
= (*i
)->note()->note();
1960 if ((*i
)->note()->note() > high_note
) {
1961 high_note
= (*i
)->note()->note();
1965 low_note
= min (low_note
, notenum
);
1966 high_note
= max (high_note
, notenum
);
1969 _no_sound_notes
= true;
1971 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1973 boost::shared_ptr
<NoteType
> note (*n
);
1974 CanvasNoteEvent
* cne
;
1975 bool select
= false;
1977 if (((1 << note
->channel()) & channel_mask
) != 0) {
1979 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1982 } else if (note
->note() == notenum
) {
1988 if ((cne
= find_canvas_note (note
)) != 0) {
1989 // extend is false because we've taken care of it,
1990 // since it extends by time range, not pitch.
1991 note_selected (cne
, add
, false);
1995 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1999 _no_sound_notes
= false;
2003 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
2005 MidiModel::Notes
& notes (_model
->notes());
2006 _optimization_iterator
= _events
.begin();
2008 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
2010 boost::shared_ptr
<NoteType
> note (*n
);
2011 CanvasNoteEvent
* cne
;
2013 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
2014 if ((cne
= find_canvas_note (note
)) != 0) {
2015 if (cne
->selected()) {
2016 note_deselected (cne
);
2018 note_selected (cne
, true, false);
2026 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
2029 clear_selection_except(ev
);
2034 if (!ev
->selected()) {
2035 add_to_selection (ev
);
2039 /* find end of latest note selected, select all between that and the start of "ev" */
2041 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2042 Evoral::MusicalTime latest
= 0;
2044 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2045 if ((*i
)->note()->end_time() > latest
) {
2046 latest
= (*i
)->note()->end_time();
2048 if ((*i
)->note()->time() < earliest
) {
2049 earliest
= (*i
)->note()->time();
2053 if (ev
->note()->end_time() > latest
) {
2054 latest
= ev
->note()->end_time();
2057 if (ev
->note()->time() < earliest
) {
2058 earliest
= ev
->note()->time();
2061 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2063 /* find notes entirely within OR spanning the earliest..latest range */
2065 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
2066 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
2067 add_to_selection (*i
);
2075 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
2077 remove_from_selection (ev
);
2081 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
2091 // TODO: Make this faster by storing the last updated selection rect, and only
2092 // adjusting things that are in the area that appears/disappeared.
2093 // We probably need a tree to be able to find events in O(log(n)) time.
2095 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2097 /* check if any corner of the note is inside the rect
2100 1) this is computing "touched by", not "contained by" the rect.
2101 2) this does not require that events be sorted in time.
2104 const double ix1
= (*i
)->x1();
2105 const double ix2
= (*i
)->x2();
2106 const double iy1
= (*i
)->y1();
2107 const double iy2
= (*i
)->y2();
2109 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2110 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
2111 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2112 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
2115 if (!(*i
)->selected()) {
2116 add_to_selection (*i
);
2118 } else if ((*i
)->selected()) {
2119 // Not inside rectangle
2120 remove_from_selection (*i
);
2126 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
2128 Selection::iterator i
= _selection
.find (ev
);
2130 if (i
!= _selection
.end()) {
2131 _selection
.erase (i
);
2134 ev
->set_selected (false);
2135 ev
->hide_velocity ();
2137 if (_selection
.empty()) {
2138 PublicEditor
& editor (trackview
.editor());
2139 editor
.get_selection().remove (this);
2144 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
2146 bool add_mrv_selection
= false;
2148 if (_selection
.empty()) {
2149 add_mrv_selection
= true;
2152 if (_selection
.insert (ev
).second
) {
2153 ev
->set_selected (true);
2154 play_midi_note ((ev
)->note());
2157 if (add_mrv_selection
) {
2158 PublicEditor
& editor (trackview
.editor());
2159 editor
.get_selection().add (this);
2164 MidiRegionView::move_selection(double dx
, double dy
, double cumulative_dy
)
2166 typedef vector
<boost::shared_ptr
<NoteType
> > PossibleChord
;
2167 PossibleChord to_play
;
2168 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2170 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2171 if ((*i
)->note()->time() < earliest
) {
2172 earliest
= (*i
)->note()->time();
2176 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2177 if (Evoral::musical_time_equal ((*i
)->note()->time(), earliest
)) {
2178 to_play
.push_back ((*i
)->note());
2180 (*i
)->move_event(dx
, dy
);
2183 if (dy
&& !_selection
.empty() && !_no_sound_notes
&& trackview
.editor().sound_notes()) {
2185 if (to_play
.size() > 1) {
2187 PossibleChord shifted
;
2189 for (PossibleChord::iterator n
= to_play
.begin(); n
!= to_play
.end(); ++n
) {
2190 boost::shared_ptr
<NoteType
> moved_note (new NoteType (**n
));
2191 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2192 shifted
.push_back (moved_note
);
2195 play_midi_chord (shifted
);
2197 } else if (!to_play
.empty()) {
2199 boost::shared_ptr
<NoteType
> moved_note (new NoteType (*to_play
.front()));
2200 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2201 play_midi_note (moved_note
);
2207 MidiRegionView::note_dropped(CanvasNoteEvent
*, frameoffset_t dt
, int8_t dnote
)
2209 assert (!_selection
.empty());
2211 uint8_t lowest_note_in_selection
= 127;
2212 uint8_t highest_note_in_selection
= 0;
2213 uint8_t highest_note_difference
= 0;
2215 // find highest and lowest notes first
2217 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2218 uint8_t pitch
= (*i
)->note()->note();
2219 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
2220 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
2224 cerr << "dnote: " << (int) dnote << endl;
2225 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2226 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2227 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2228 << int(highest_note_in_selection) << endl;
2229 cerr << "selection size: " << _selection.size() << endl;
2230 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2233 // Make sure the note pitch does not exceed the MIDI standard range
2234 if (highest_note_in_selection
+ dnote
> 127) {
2235 highest_note_difference
= highest_note_in_selection
- 127;
2238 start_note_diff_command (_("move notes"));
2240 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
2242 Evoral::MusicalTime new_time
= frames_to_beats (beats_to_frames ((*i
)->note()->time()) + dt
);
2248 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2250 uint8_t original_pitch
= (*i
)->note()->note();
2251 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
2253 // keep notes in standard midi range
2254 clamp_to_0_127(new_pitch
);
2256 // keep original pitch if note is dragged outside valid midi range
2257 if ((original_pitch
!= 0 && new_pitch
== 0)
2258 || (original_pitch
!= 127 && new_pitch
== 127)) {
2259 new_pitch
= original_pitch
;
2262 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2263 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2265 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::NoteNumber
, new_pitch
);
2270 // care about notes being moved beyond the upper/lower bounds on the canvas
2271 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2272 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2273 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2278 MidiRegionView::snap_pixel_to_frame(double x
)
2280 PublicEditor
& editor (trackview
.editor());
2281 return snap_frame_to_frame (editor
.pixel_to_frame (x
));
2284 /** Snap a frame offset within our region using the current snap settings.
2285 * @param x Frame offset from this region's position.
2286 * @return Snapped frame offset from this region's position.
2289 MidiRegionView::snap_frame_to_frame (frameoffset_t x
)
2291 PublicEditor
& editor
= trackview
.editor();
2293 /* x is region relative, convert it to global absolute frames */
2294 framepos_t
const session_frame
= x
+ _region
->position();
2296 /* try a snap in either direction */
2297 framepos_t frame
= session_frame
;
2298 editor
.snap_to (frame
, 0);
2300 /* if we went off the beginning of the region, snap forwards */
2301 if (frame
< _region
->position ()) {
2302 frame
= session_frame
;
2303 editor
.snap_to (frame
, 1);
2306 /* back to region relative */
2307 return frame
- _region
->position();
2311 MidiRegionView::snap_to_pixel(double x
)
2313 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2317 MidiRegionView::get_position_pixels()
2319 framepos_t region_frame
= get_position();
2320 return trackview
.editor().frame_to_pixel(region_frame
);
2324 MidiRegionView::get_end_position_pixels()
2326 framepos_t frame
= get_position() + get_duration ();
2327 return trackview
.editor().frame_to_pixel(frame
);
2331 MidiRegionView::beats_to_frames(double beats
) const
2333 return _time_converter
.to(beats
);
2337 MidiRegionView::frames_to_beats(framepos_t frames
) const
2339 return _time_converter
.from(frames
);
2343 MidiRegionView::begin_resizing (bool /*at_front*/)
2345 _resize_data
.clear();
2347 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2348 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2350 // only insert CanvasNotes into the map
2352 NoteResizeData
*resize_data
= new NoteResizeData();
2353 resize_data
->canvas_note
= note
;
2355 // create a new SimpleRect from the note which will be the resize preview
2356 SimpleRect
*resize_rect
= new SimpleRect(
2357 *_note_group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2359 // calculate the colors: get the color settings
2360 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2361 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2364 // make the resize preview notes more transparent and bright
2365 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2367 // calculate color based on note velocity
2368 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2369 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2373 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2374 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2376 resize_data
->resize_rect
= resize_rect
;
2377 _resize_data
.push_back(resize_data
);
2382 /** Update resizing notes while user drags.
2383 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2384 * @param at_front which end of the note (true == note on, false == note off)
2385 * @param delta_x change in mouse position since the start of the drag
2386 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2387 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2388 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2389 * as the \a primary note.
2392 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2394 bool cursor_set
= false;
2396 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2397 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2398 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2403 current_x
= canvas_note
->x1() + delta_x
;
2405 current_x
= primary
->x1() + delta_x
;
2409 current_x
= canvas_note
->x2() + delta_x
;
2411 current_x
= primary
->x2() + delta_x
;
2416 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2417 resize_rect
->property_x2() = canvas_note
->x2();
2419 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2420 resize_rect
->property_x1() = canvas_note
->x1();
2426 beats
= snap_pixel_to_frame (current_x
);
2427 beats
= frames_to_beats (beats
);
2432 if (beats
< canvas_note
->note()->end_time()) {
2433 len
= canvas_note
->note()->time() - beats
;
2434 len
+= canvas_note
->note()->length();
2439 if (beats
>= canvas_note
->note()->time()) {
2440 len
= beats
- canvas_note
->note()->time();
2447 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2448 show_verbose_cursor (buf
, 0, 0);
2457 /** Finish resizing notes when the user releases the mouse button.
2458 * Parameters the same as for \a update_resizing().
2461 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2463 start_note_diff_command (_("resize notes"));
2465 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2466 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2467 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2469 /* Get the new x position for this resize, which is in pixels relative
2470 * to the region position.
2477 current_x
= canvas_note
->x1() + delta_x
;
2479 current_x
= primary
->x1() + delta_x
;
2483 current_x
= canvas_note
->x2() + delta_x
;
2485 current_x
= primary
->x2() + delta_x
;
2489 /* Convert that to a frame within the region */
2490 current_x
= snap_pixel_to_frame (current_x
) + _region
->start ();
2492 /* and then to beats */
2493 current_x
= frames_to_beats (current_x
);
2495 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2496 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::StartTime
, current_x
);
2498 double len
= canvas_note
->note()->time() - current_x
;
2499 len
+= canvas_note
->note()->length();
2502 /* XXX convert to beats */
2503 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2508 double len
= current_x
- canvas_note
->note()->time();
2511 /* XXX convert to beats */
2512 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2520 _resize_data
.clear();
2525 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2527 uint8_t new_velocity
;
2530 new_velocity
= event
->note()->velocity() + velocity
;
2531 clamp_to_0_127(new_velocity
);
2533 new_velocity
= velocity
;
2536 event
->set_selected (event
->selected()); // change color
2538 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Velocity
, new_velocity
);
2542 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2547 new_note
= event
->note()->note() + note
;
2552 clamp_to_0_127 (new_note
);
2553 note_diff_add_change (event
, MidiModel::NoteDiffCommand::NoteNumber
, new_note
);
2557 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2559 bool change_start
= false;
2560 bool change_length
= false;
2561 Evoral::MusicalTime new_start
= 0;
2562 Evoral::MusicalTime new_length
= 0;
2564 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2566 front_delta: if positive - move the start of the note later in time (shortening it)
2567 if negative - move the start of the note earlier in time (lengthening it)
2569 end_delta: if positive - move the end of the note later in time (lengthening it)
2570 if negative - move the end of the note earlier in time (shortening it)
2574 if (front_delta
< 0) {
2576 if (event
->note()->time() < -front_delta
) {
2579 new_start
= event
->note()->time() + front_delta
; // moves earlier
2582 /* start moved toward zero, so move the end point out to where it used to be.
2583 Note that front_delta is negative, so this increases the length.
2586 new_length
= event
->note()->length() - front_delta
;
2587 change_start
= true;
2588 change_length
= true;
2592 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2594 if (new_pos
< event
->note()->end_time()) {
2595 new_start
= event
->note()->time() + front_delta
;
2596 /* start moved toward the end, so move the end point back to where it used to be */
2597 new_length
= event
->note()->length() - front_delta
;
2598 change_start
= true;
2599 change_length
= true;
2606 bool can_change
= true;
2607 if (end_delta
< 0) {
2608 if (event
->note()->length() < -end_delta
) {
2614 new_length
= event
->note()->length() + end_delta
;
2615 change_length
= true;
2620 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_start
);
2623 if (change_length
) {
2624 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, new_length
);
2629 MidiRegionView::change_note_channel (CanvasNoteEvent
* event
, int8_t chn
, bool relative
)
2631 uint8_t new_channel
;
2635 if (event
->note()->channel() < -chn
) {
2638 new_channel
= event
->note()->channel() + chn
;
2641 new_channel
= event
->note()->channel() + chn
;
2644 new_channel
= (uint8_t) chn
;
2647 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Channel
, new_channel
);
2651 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2653 Evoral::MusicalTime new_time
;
2657 if (event
->note()->time() < -delta
) {
2660 new_time
= event
->note()->time() + delta
;
2663 new_time
= event
->note()->time() + delta
;
2669 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2673 MidiRegionView::change_note_length (CanvasNoteEvent
* event
, Evoral::MusicalTime t
)
2675 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, t
);
2679 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2683 if (_selection
.empty()) {
2698 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2699 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2705 start_note_diff_command (_("change velocities"));
2707 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2708 Selection::iterator next
= i
;
2710 change_note_velocity (*i
, delta
, true);
2716 if (!_selection
.empty()) {
2718 snprintf (buf
, sizeof (buf
), "Vel %d",
2719 (int) (*_selection
.begin())->note()->velocity());
2720 show_verbose_cursor (buf
, 10, 10);
2726 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2728 if (_selection
.empty()) {
2745 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2747 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2751 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2758 start_note_diff_command (_("transpose"));
2760 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2761 Selection::iterator next
= i
;
2763 change_note_note (*i
, delta
, true);
2771 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, Evoral::MusicalTime delta
, bool start
, bool end
)
2777 /* grab the current grid distance */
2779 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2781 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2782 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2792 start_note_diff_command (_("change note lengths"));
2794 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2795 Selection::iterator next
= i
;
2798 /* note the negation of the delta for start */
2800 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2809 MidiRegionView::nudge_notes (bool forward
)
2811 if (_selection
.empty()) {
2815 /* pick a note as the point along the timeline to get the nudge distance.
2816 its not necessarily the earliest note, so we may want to pull the notes out
2817 into a vector and sort before using the first one.
2820 framepos_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2822 framepos_t distance
;
2824 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2826 /* grid is off - use nudge distance */
2828 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2834 framepos_t next_pos
= ref_point
;
2837 if (max_framepos
- 1 < next_pos
) {
2841 if (next_pos
== 0) {
2847 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2848 distance
= ref_point
- next_pos
;
2851 if (distance
== 0) {
2855 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2861 start_note_diff_command (_("nudge"));
2863 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2864 Selection::iterator next
= i
;
2866 change_note_time (*i
, delta
, true);
2874 MidiRegionView::change_channel(uint8_t channel
)
2876 start_note_diff_command(_("change channel"));
2877 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2878 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::Channel
, channel
);
2886 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2888 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2890 _pre_enter_cursor
= editor
->get_canvas_cursor ();
2892 if (_mouse_state
== SelectTouchDragging
) {
2893 note_selected (ev
, true);
2896 show_verbose_cursor (ev
->note ());
2900 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2902 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2904 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2905 (*i
)->hide_velocity ();
2908 editor
->verbose_cursor()->hide ();
2910 if (_pre_enter_cursor
) {
2911 editor
->set_canvas_cursor (_pre_enter_cursor
);
2912 _pre_enter_cursor
= 0;
2917 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange
* ev
)
2920 s
<< ((int) ev
->patch()->program() + 1) << ":" << (ev
->patch()->bank() + 1);
2921 show_verbose_cursor (s
.str(), 10, 20);
2925 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange
*)
2927 trackview
.editor().verbose_cursor()->hide ();
2931 MidiRegionView::note_mouse_position (float x_fraction
, float /*y_fraction*/, bool can_set_cursor
)
2933 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2935 if (x_fraction
> 0.0 && x_fraction
< 0.25) {
2936 editor
->set_canvas_cursor (editor
->cursors()->left_side_trim
);
2937 } else if (x_fraction
>= 0.75 && x_fraction
< 1.0) {
2938 editor
->set_canvas_cursor (editor
->cursors()->right_side_trim
);
2940 if (_pre_enter_cursor
&& can_set_cursor
) {
2941 editor
->set_canvas_cursor (_pre_enter_cursor
);
2947 MidiRegionView::set_frame_color()
2951 TimeAxisViewItem::set_frame_color ();
2958 f
= ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2959 } else if (high_enough_for_name
) {
2960 f
= ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2965 if (!rect_visible
) {
2966 f
= UINT_RGBA_CHANGE_A (f
, 0);
2969 frame
->property_fill_color_rgba() = f
;
2973 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2977 case FilterChannels
:
2978 _force_channel
= -1;
2981 _force_channel
= mask
;
2982 mask
= 0xFFFF; // Show all notes as active (below)
2985 // Update notes for selection
2986 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2987 (*i
)->on_channel_selection_change(mask
);
2990 _last_channel_selection
= mask
;
2992 _patch_changes
.clear ();
2993 display_patch_changes ();
2997 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2999 _model_name
= model
;
3000 _custom_device_mode
= custom_device_mode
;
3005 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
3007 if (_selection
.empty()) {
3011 PublicEditor
& editor (trackview
.editor());
3015 /* XXX what to do ? */
3019 editor
.get_cut_buffer().add (selection_as_cut_buffer());
3027 start_note_diff_command();
3029 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3036 note_diff_remove_note (*i
);
3046 MidiRegionView::selection_as_cut_buffer () const
3050 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3051 NoteType
* n
= (*i
)->note().get();
3052 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
3055 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
3061 /** This method handles undo */
3063 MidiRegionView::paste (framepos_t pos
, float times
, const MidiCutBuffer
& mcb
)
3069 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("MIDI paste @ %1 times %2\n", pos
, times
));
3071 trackview
.session()->begin_reversible_command (_("paste"));
3073 start_note_diff_command (_("paste"));
3075 Evoral::MusicalTime beat_delta
;
3076 Evoral::MusicalTime paste_pos_beats
;
3077 Evoral::MusicalTime duration
;
3078 Evoral::MusicalTime end_point
= 0;
3080 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
3081 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
3082 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
3083 paste_pos_beats
= 0;
3085 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",
3086 (*mcb
.notes().begin())->time(),
3087 (*mcb
.notes().rbegin())->end_time(),
3088 duration
, pos
, _region
->position(),
3089 paste_pos_beats
, beat_delta
));
3093 for (int n
= 0; n
< (int) times
; ++n
) {
3095 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
3097 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
3098 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
3100 /* make all newly added notes selected */
3102 note_diff_add_note (copied_note
, true);
3103 end_point
= copied_note
->end_time();
3106 paste_pos_beats
+= duration
;
3109 /* if we pasted past the current end of the region, extend the region */
3111 framepos_t end_frame
= _region
->position() + beats_to_frames (end_point
);
3112 framepos_t region_end
= _region
->position() + _region
->length() - 1;
3114 if (end_frame
> region_end
) {
3116 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste extended region from %1 to %2\n", region_end
, end_frame
));
3118 _region
->clear_changes ();
3119 _region
->set_length (end_frame
);
3120 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
3125 trackview
.session()->commit_reversible_command ();
3128 struct EventNoteTimeEarlyFirstComparator
{
3129 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
3130 return a
->note()->time() < b
->note()->time();
3135 MidiRegionView::time_sort_events ()
3137 if (!_sort_needed
) {
3141 EventNoteTimeEarlyFirstComparator cmp
;
3144 _sort_needed
= false;
3148 MidiRegionView::goto_next_note (bool add_to_selection
)
3150 bool use_next
= false;
3152 if (_events
.back()->selected()) {
3156 time_sort_events ();
3158 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3159 if ((*i
)->selected()) {
3162 } else if (use_next
) {
3163 if (!add_to_selection
) {
3166 note_selected (*i
, true, false);
3172 /* use the first one */
3174 unique_select (_events
.front());
3179 MidiRegionView::goto_previous_note (bool add_to_selection
)
3181 bool use_next
= false;
3183 if (_events
.front()->selected()) {
3187 time_sort_events ();
3189 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
3190 if ((*i
)->selected()) {
3193 } else if (use_next
) {
3194 if (!add_to_selection
) {
3197 note_selected (*i
, true, false);
3203 /* use the last one */
3205 unique_select (*(_events
.rbegin()));
3209 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
3211 bool had_selected
= false;
3213 time_sort_events ();
3215 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3216 if ((*i
)->selected()) {
3217 selected
.insert ((*i
)->note());
3218 had_selected
= true;
3222 if (allow_all_if_none_selected
&& !had_selected
) {
3223 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3224 selected
.insert ((*i
)->note());
3230 MidiRegionView::update_ghost_note (double x
, double y
)
3232 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3237 _note_group
->w2i (x
, y
);
3238 framepos_t
const f
= snap_pixel_to_frame (x
);
3241 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
3247 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
3249 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
3250 _ghost_note
->note()->set_length (length
);
3251 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
3252 _ghost_note
->note()->set_channel (mtv
->get_channel_for_add ());
3254 /* the ghost note does not appear in ghost regions, so pass false in here */
3255 update_note (_ghost_note
, false);
3257 show_verbose_cursor (_ghost_note
->note ());
3261 MidiRegionView::create_ghost_note (double x
, double y
)
3266 boost::shared_ptr
<NoteType
> g (new NoteType
);
3267 _ghost_note
= new NoEventCanvasNote (*this, *_note_group
, g
);
3268 _ghost_note
->property_outline_color_rgba() = 0x000000aa;
3269 update_ghost_note (x
, y
);
3270 _ghost_note
->show ();
3275 show_verbose_cursor (_ghost_note
->note ());
3279 MidiRegionView::snap_changed ()
3285 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
3289 MidiRegionView::drop_down_keys ()
3291 _mouse_state
= None
;
3295 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double /*x*/, double y
)
3297 double note
= midi_stream_view()->y_to_note(y
);
3299 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3301 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
3303 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
3304 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3305 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
3306 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3311 bool add_mrv_selection
= false;
3313 if (_selection
.empty()) {
3314 add_mrv_selection
= true;
3317 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
3318 if (_selection
.insert (*i
).second
) {
3319 (*i
)->set_selected (true);
3323 if (add_mrv_selection
) {
3324 PublicEditor
& editor (trackview
.editor());
3325 editor
.get_selection().add (this);
3330 MidiRegionView::color_handler ()
3332 RegionView::color_handler ();
3334 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3335 (*i
)->set_selected ((*i
)->selected()); // will change color
3338 /* XXX probably more to do here */
3342 MidiRegionView::enable_display (bool yn
)
3344 RegionView::enable_display (yn
);
3351 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos
)
3353 if (_step_edit_cursor
== 0) {
3354 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
3356 _step_edit_cursor
= new ArdourCanvas::SimpleRect (*group
);
3357 _step_edit_cursor
->property_y1() = 0;
3358 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
3359 _step_edit_cursor
->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3360 _step_edit_cursor
->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3363 move_step_edit_cursor (pos
);
3364 _step_edit_cursor
->show ();
3368 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos
)
3370 _step_edit_cursor_position
= pos
;
3372 if (_step_edit_cursor
) {
3373 double pixel
= trackview
.editor().frame_to_pixel (beats_to_frames (pos
));
3374 _step_edit_cursor
->property_x1() = pixel
;
3375 set_step_edit_cursor_width (_step_edit_cursor_width
);
3380 MidiRegionView::hide_step_edit_cursor ()
3382 if (_step_edit_cursor
) {
3383 _step_edit_cursor
->hide ();
3388 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats
)
3390 _step_edit_cursor_width
= beats
;
3392 if (_step_edit_cursor
) {
3393 _step_edit_cursor
->property_x2() = _step_edit_cursor
->property_x1() + trackview
.editor().frame_to_pixel (beats_to_frames (beats
));
3397 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3398 * @param buf Data that has been recorded.
3399 * @param w Source that this data will end up in.
3402 MidiRegionView::data_recorded (boost::shared_ptr
<MidiBuffer
> buf
, boost::weak_ptr
<MidiSource
> w
)
3404 if (!_active_notes
) {
3405 /* we aren't actively being recorded to */
3409 boost::shared_ptr
<MidiSource
> src
= w
.lock ();
3410 if (!src
|| src
!= midi_region()->midi_source()) {
3411 /* recorded data was not destined for our source */
3415 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*> (&trackview
);
3416 BeatsFramesConverter
converter (trackview
.session()->tempo_map(), mtv
->midi_track()->get_capture_start_frame (0));
3418 framepos_t back
= max_framepos
;
3420 for (MidiBuffer::iterator i
= buf
->begin(); i
!= buf
->end(); ++i
) {
3421 Evoral::MIDIEvent
<MidiBuffer::TimeType
> const ev (*i
, false);
3422 assert (ev
.buffer ());
3424 Evoral::MusicalTime
const time_beats
= converter
.from (ev
.time () - converter
.origin_b ());
3426 if (ev
.type() == MIDI_CMD_NOTE_ON
) {
3428 boost::shared_ptr
<NoteType
> note (
3429 new NoteType (ev
.channel(), time_beats
, 0, ev
.note(), ev
.velocity())
3432 add_note (note
, true);
3434 /* fix up our note range */
3435 if (ev
.note() < _current_range_min
) {
3436 midi_stream_view()->apply_note_range (ev
.note(), _current_range_max
, true);
3437 } else if (ev
.note() > _current_range_max
) {
3438 midi_stream_view()->apply_note_range (_current_range_min
, ev
.note(), true);
3441 } else if (ev
.type() == MIDI_CMD_NOTE_OFF
) {
3442 resolve_note (ev
.note (), time_beats
);
3448 midi_stream_view()->check_record_layers (region(), back
);
3452 MidiRegionView::trim_front_starting ()
3454 /* Reparent the note group to the region view's parent, so that it doesn't change
3455 when the region view is trimmed.
3457 _temporary_note_group
= new ArdourCanvas::Group (*group
->property_parent ());
3458 _temporary_note_group
->move (group
->property_x(), group
->property_y());
3459 _note_group
->reparent (*_temporary_note_group
);
3463 MidiRegionView::trim_front_ending ()
3465 _note_group
->reparent (*group
);
3466 delete _temporary_note_group
;
3467 _temporary_note_group
= 0;
3469 if (_region
->start() < 0) {
3470 /* Trim drag made start time -ve; fix this */
3471 midi_region()->fix_negative_start ();
3476 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange
* pc
)
3478 PatchChangeDialog
d (&_time_converter
, trackview
.session(), *pc
->patch (), Gtk::Stock::APPLY
);
3479 if (d
.run () != Gtk::RESPONSE_ACCEPT
) {
3483 change_patch_change (pc
->patch(), d
.patch ());
3488 MidiRegionView::show_verbose_cursor (boost::shared_ptr
<NoteType
> n
) const
3491 snprintf (buf
, sizeof (buf
), "%s (%d) Chn %d\nVel %d",
3492 Evoral::midi_note_name (n
->note()).c_str(),
3494 (int) n
->channel() + 1,
3495 (int) n
->velocity());
3497 show_verbose_cursor (buf
, 10, 20);
3501 MidiRegionView::show_verbose_cursor (string
const & text
, double xoffset
, double yoffset
) const
3505 trackview
.editor().get_pointer_position (wx
, wy
);
3510 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3512 double x1
, y1
, x2
, y2
;
3513 trackview
.editor().verbose_cursor()->canvas_item()->get_bounds (x1
, y1
, x2
, y2
);
3515 if ((wy
+ y2
- y1
) > trackview
.editor().canvas_height()) {
3516 wy
-= (y2
- y1
) + 2 * yoffset
;
3519 trackview
.editor().verbose_cursor()->set (text
, wx
, wy
);
3520 trackview
.editor().verbose_cursor()->show ();