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
;
414 framepos_t event_frame
= 0;
416 if (ev
->button
!= 1) {
423 group
->w2i(event_x
, event_y
);
424 group
->ungrab(ev
->time
);
426 event_frame
= trackview
.editor().pixel_to_frame(event_x
);
428 switch (_mouse_state
) {
429 case Pressed
: // Clicked
431 switch (trackview
.editor().current_mouse_mode()) {
437 if (Keyboard::is_insert_note_event(ev
)) {
439 double event_x
, event_y
;
443 group
->w2i(event_x
, event_y
);
446 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
452 create_note_at (event_x
, event_y
, beats
, true);
460 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
466 create_note_at (event_x
, event_y
, beats
, true);
477 case SelectRectDragging
: // Select drag done
484 case AddDragging
: // Add drag done
488 if (Keyboard::is_insert_note_event(ev
) || trackview
.editor().current_mouse_mode() == MouseRange
) {
490 if (_drag_rect
->property_x2() > _drag_rect
->property_x1() + 2) {
492 const double x
= _drag_rect
->property_x1();
493 const double length
= trackview
.editor().pixel_to_frame (_drag_rect
->property_x2() - _drag_rect
->property_x1());
495 create_note_at (x
, _drag_rect
->property_y1(), frames_to_beats(length
), true);
502 create_ghost_note (ev
->x
, ev
->y
);
512 MidiRegionView::motion (GdkEventMotion
* ev
)
514 double event_x
, event_y
;
515 framepos_t event_frame
= 0;
519 group
->w2i(event_x
, event_y
);
521 // convert event_x to global frame
522 event_frame
= snap_pixel_to_frame (event_x
);
524 if (!_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
525 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())
526 && _mouse_state
!= AddDragging
) {
528 create_ghost_note (ev
->x
, ev
->y
);
529 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
530 && Keyboard::modifier_state_contains (ev
->state
, Keyboard::insert_note_modifier())) {
532 update_ghost_note (ev
->x
, ev
->y
);
533 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() != MouseRange
) {
538 trackview
.editor().verbose_cursor()->hide ();
539 } else if (_ghost_note
&& trackview
.editor().current_mouse_mode() == MouseRange
) {
540 update_ghost_note (ev
->x
, ev
->y
);
543 /* any motion immediately hides velocity text that may have been visible */
545 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
546 (*i
)->hide_velocity ();
549 switch (_mouse_state
) {
550 case Pressed
: // Maybe start a drag, if we've moved a bit
552 if (fabs (event_x
- _last_x
) < 1 && fabs (event_y
- _last_y
) < 1) {
553 /* 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())) {
561 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
562 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
566 _drag_start_x
= event_x
;
567 _drag_start_y
= event_y
;
569 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
570 _drag_rect
->property_x1() = event_x
;
571 _drag_rect
->property_y1() = event_y
;
572 _drag_rect
->property_x2() = event_x
;
573 _drag_rect
->property_y2() = event_y
;
574 _drag_rect
->property_outline_what() = 0xFF;
575 _drag_rect
->property_outline_color_rgba()
576 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
577 _drag_rect
->property_fill_color_rgba()
578 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
580 _mouse_state
= SelectRectDragging
;
583 } else if (trackview
.editor().internal_editing()) {
584 // Add note drag start
589 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
590 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
594 _drag_start_x
= event_x
;
595 _drag_start_y
= event_y
;
597 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
598 _drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
600 _drag_rect
->property_y1() = midi_stream_view()->note_to_y(
601 midi_stream_view()->y_to_note(event_y
));
602 _drag_rect
->property_x2() = trackview
.editor().frame_to_pixel(event_frame
);
603 _drag_rect
->property_y2() = _drag_rect
->property_y1()
604 + floor(midi_stream_view()->note_height());
605 _drag_rect
->property_outline_what() = 0xFF;
606 _drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
607 _drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
609 _mouse_state
= AddDragging
;
616 trackview
.editor().verbose_cursor()->hide ();
624 case SelectRectDragging
: // Select drag motion
625 case AddDragging
: // Add note drag motion
630 GdkModifierType state
;
631 gdk_window_get_pointer(ev
->window
, &t_x
, &t_y
, &state
);
636 if (_mouse_state
== AddDragging
) {
637 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
642 if (event_x
> _drag_start_x
) {
643 _drag_rect
->property_x2() = event_x
;
646 _drag_rect
->property_x1() = event_x
;
650 if (_drag_rect
&& _mouse_state
== SelectRectDragging
) {
652 if (event_y
> _drag_start_y
) {
653 _drag_rect
->property_y2() = event_y
;
656 _drag_rect
->property_y1() = event_y
;
659 update_drag_selection(_drag_start_x
, event_x
, _drag_start_y
, event_y
);
665 case SelectTouchDragging
:
677 MidiRegionView::scroll (GdkEventScroll
* ev
)
679 if (_selection
.empty()) {
683 trackview
.editor().verbose_cursor()->hide ();
685 bool fine
= !Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
);
687 if (ev
->direction
== GDK_SCROLL_UP
) {
688 change_velocities (true, fine
, false);
689 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
690 change_velocities (false, fine
, false);
696 MidiRegionView::key_press (GdkEventKey
* ev
)
698 /* since GTK bindings are generally activated on press, and since
699 detectable auto-repeat is the name of the game and only sends
700 repeated presses, carry out key actions at key press, not release.
703 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
704 _mouse_state
= SelectTouchDragging
;
707 } else if (ev
->keyval
== GDK_Escape
) {
711 } else if (ev
->keyval
== GDK_comma
|| ev
->keyval
== GDK_period
) {
713 bool start
= (ev
->keyval
== GDK_comma
);
714 bool end
= (ev
->keyval
== GDK_period
);
715 bool shorter
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
);
716 bool fine
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
718 change_note_lengths (fine
, shorter
, 0.0, start
, end
);
722 } else if (ev
->keyval
== GDK_Delete
) {
727 } else if (ev
->keyval
== GDK_Tab
) {
729 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
730 goto_previous_note ();
736 } else if (ev
->keyval
== GDK_Up
) {
738 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
739 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
741 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
742 change_velocities (true, fine
, allow_smush
);
744 transpose (true, fine
, allow_smush
);
748 } else if (ev
->keyval
== GDK_Down
) {
750 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
751 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
753 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
754 change_velocities (false, fine
, allow_smush
);
756 transpose (false, fine
, allow_smush
);
760 } else if (ev
->keyval
== GDK_Left
) {
765 } else if (ev
->keyval
== GDK_Right
) {
770 } else if (ev
->keyval
== GDK_Control_L
) {
773 } else if (ev
->keyval
== GDK_c
) {
782 MidiRegionView::key_release (GdkEventKey
* ev
)
784 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
792 MidiRegionView::channel_edit ()
796 uint8_t current_channel
;
798 if (_selection
.empty()) {
802 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
803 Selection::iterator next
= i
;
805 current_channel
= (*i
)->note()->channel ();
808 if (current_channel
!= (*i
)->note()->channel()) {
814 MidiChannelDialog
channel_dialog (current_channel
);
815 int ret
= channel_dialog
.run ();
818 case Gtk::RESPONSE_OK
:
824 uint8_t new_channel
= channel_dialog
.active_channel ();
826 start_note_diff_command (_("channel edit"));
828 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
829 Selection::iterator next
= i
;
831 change_note_channel (*i
, new_channel
);
839 MidiRegionView::show_list_editor ()
842 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
844 _list_editor
->present ();
847 /** Add a note to the model, and the view, at a canvas (click) coordinate.
848 * \param x horizontal position in pixels
849 * \param y vertical position in pixels
850 * \param length duration of the note in beats, which will be snapped to the grid
851 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
854 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
856 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
857 MidiStreamView
* const view
= mtv
->midi_view();
859 double note
= view
->y_to_note(y
);
862 assert(note
<= 127.0);
864 // Start of note in frames relative to region start
865 framepos_t
const start_frames
= snap_pixel_to_frame (x
);
866 assert(start_frames
>= 0);
869 length
= frames_to_beats(
870 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
872 assert (length
!= 0);
875 length
= frames_to_beats (beats_to_frames (length
) - 1);
878 const boost::shared_ptr
<NoteType
> new_note (new NoteType (mtv
->get_channel_for_add (),
879 frames_to_beats(start_frames
+ _region
->start()), length
,
880 (uint8_t)note
, 0x40));
882 if (_model
->contains (new_note
)) {
886 view
->update_note_range(new_note
->note());
888 MidiModel::NoteDiffCommand
* cmd
= _model
->new_note_diff_command("add note");
890 _model
->apply_command(*trackview
.session(), cmd
);
892 play_midi_note (new_note
);
896 MidiRegionView::clear_events()
901 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
902 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
907 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
912 _patch_changes
.clear();
914 _optimization_iterator
= _events
.end();
918 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
922 content_connection
.disconnect ();
923 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
927 if (_enable_display
) {
933 MidiRegionView::start_note_diff_command (string name
)
935 if (!_note_diff_command
) {
936 _note_diff_command
= _model
->new_note_diff_command (name
);
941 MidiRegionView::note_diff_add_note (const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
943 if (_note_diff_command
) {
944 _note_diff_command
->add (note
);
947 _marked_for_selection
.insert(note
);
950 _marked_for_velocity
.insert(note
);
955 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent
* ev
)
957 if (_note_diff_command
&& ev
->note()) {
958 _note_diff_command
->remove(ev
->note());
963 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
964 MidiModel::NoteDiffCommand::Property property
,
967 if (_note_diff_command
) {
968 _note_diff_command
->change (ev
->note(), property
, val
);
973 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
974 MidiModel::NoteDiffCommand::Property property
,
975 Evoral::MusicalTime val
)
977 if (_note_diff_command
) {
978 _note_diff_command
->change (ev
->note(), property
, val
);
983 MidiRegionView::apply_diff (bool as_subcommand
)
987 if (!_note_diff_command
) {
991 if ((add_or_remove
= _note_diff_command
->adds_or_removes())) {
992 // Mark all selected notes for selection when model reloads
993 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
994 _marked_for_selection
.insert((*i
)->note());
999 _model
->apply_command_as_subcommand (*trackview
.session(), _note_diff_command
);
1001 _model
->apply_command (*trackview
.session(), _note_diff_command
);
1004 _note_diff_command
= 0;
1005 midi_view()->midi_track()->playlist_modified();
1007 if (add_or_remove
) {
1008 _marked_for_selection
.clear();
1011 _marked_for_velocity
.clear();
1015 MidiRegionView::abort_command()
1017 delete _note_diff_command
;
1018 _note_diff_command
= 0;
1023 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
1025 if (_optimization_iterator
!= _events
.end()) {
1026 ++_optimization_iterator
;
1029 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
1030 return *_optimization_iterator
;
1033 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
1034 if ((*_optimization_iterator
)->note() == note
) {
1035 return *_optimization_iterator
;
1043 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
1045 MidiModel::Notes notes
;
1046 _model
->get_notes (notes
, op
, val
, chan_mask
);
1048 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1049 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
1057 MidiRegionView::redisplay_model()
1059 // Don't redisplay the model if we're currently recording and displaying that
1060 if (_active_notes
) {
1068 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1069 (*i
)->invalidate ();
1072 MidiModel::ReadLock
lock(_model
->read_lock());
1074 MidiModel::Notes
& notes (_model
->notes());
1075 _optimization_iterator
= _events
.begin();
1077 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1079 boost::shared_ptr
<NoteType
> note (*n
);
1080 CanvasNoteEvent
* cne
;
1083 if (note_in_region_range (note
, visible
)) {
1085 if ((cne
= find_canvas_note (note
)) != 0) {
1092 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
1094 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
1106 add_note (note
, visible
);
1111 if ((cne
= find_canvas_note (note
)) != 0) {
1119 /* remove note items that are no longer valid */
1121 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
1122 if (!(*i
)->valid ()) {
1124 i
= _events
.erase (i
);
1130 _patch_changes
.clear();
1134 display_patch_changes ();
1136 _marked_for_selection
.clear ();
1137 _marked_for_velocity
.clear ();
1139 /* we may have caused _events to contain things out of order (e.g. if a note
1140 moved earlier or later). we don't generally need them in time order, but
1141 make a note that a sort is required for those cases that require it.
1144 _sort_needed
= true;
1148 MidiRegionView::display_patch_changes ()
1150 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1151 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
1153 for (uint8_t i
= 0; i
< 16; ++i
) {
1154 if (chn_mask
& (1<<i
)) {
1155 display_patch_changes_on_channel (i
);
1161 MidiRegionView::display_patch_changes_on_channel (uint8_t channel
)
1163 for (MidiModel::PatchChanges::const_iterator i
= _model
->patch_changes().begin(); i
!= _model
->patch_changes().end(); ++i
) {
1165 if ((*i
)->channel() != channel
) {
1169 MIDI::Name::PatchPrimaryKey
patch_key ((*i
)->bank_msb (), (*i
)->bank_lsb (), (*i
)->program ());
1171 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1172 MIDI::Name::MidiPatchManager::instance().find_patch(
1173 _model_name
, _custom_device_mode
, channel
, patch_key
);
1176 add_canvas_patch_change (*i
, patch
->name());
1179 /* program and bank numbers are zero-based: convert to one-based */
1180 snprintf (buf
, 16, "%d\n%d", (*i
)->program() + 1, (*i
)->bank() + 1);
1181 add_canvas_patch_change (*i
, buf
);
1187 MidiRegionView::display_sysexes()
1189 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1190 Evoral::MusicalTime time
= (*i
)->time();
1195 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1196 str
<< int((*i
)->buffer()[b
]);
1197 if (b
!= (*i
)->size() -1) {
1201 string text
= str
.str();
1203 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1205 double height
= midi_stream_view()->contents_height();
1207 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1208 new CanvasSysEx(*this, *_note_group
, text
, height
, x
, 1.0));
1210 // Show unless patch change is beyond the region bounds
1211 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1217 _sys_exes
.push_back(sysex
);
1222 MidiRegionView::~MidiRegionView ()
1224 in_destructor
= true;
1226 trackview
.editor().verbose_cursor()->hide ();
1228 note_delete_connection
.disconnect ();
1230 delete _list_editor
;
1232 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1234 if (_active_notes
) {
1242 delete _note_diff_command
;
1243 delete _step_edit_cursor
;
1244 delete _temporary_note_group
;
1248 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1250 RegionView::region_resized(what_changed
);
1252 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1253 set_duration(_region
->length(), 0);
1254 if (_enable_display
) {
1261 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1263 RegionView::reset_width_dependent_items(pixel_width
);
1264 assert(_pixel_width
== pixel_width
);
1266 if (_enable_display
) {
1270 move_step_edit_cursor (_step_edit_cursor_position
);
1271 set_step_edit_cursor_width (_step_edit_cursor_width
);
1275 MidiRegionView::set_height (double height
)
1277 static const double FUDGE
= 2.0;
1278 const double old_height
= _height
;
1279 RegionView::set_height(height
);
1280 _height
= height
- FUDGE
;
1282 apply_note_range(midi_stream_view()->lowest_note(),
1283 midi_stream_view()->highest_note(),
1284 height
!= old_height
+ FUDGE
);
1287 name_pixbuf
->raise_to_top();
1290 for (PatchChanges::iterator x
= _patch_changes
.begin(); x
!= _patch_changes
.end(); ++x
) {
1291 (*x
)->set_height (midi_stream_view()->contents_height());
1294 if (_step_edit_cursor
) {
1295 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
1300 /** Apply the current note range from the stream view
1301 * by repositioning/hiding notes as necessary
1304 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1306 if (!_enable_display
) {
1310 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1314 _current_range_min
= min
;
1315 _current_range_max
= max
;
1317 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1318 CanvasNoteEvent
* event
= *i
;
1319 boost::shared_ptr
<NoteType
> note (event
->note());
1321 if (note
->note() < _current_range_min
||
1322 note
->note() > _current_range_max
) {
1328 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1330 const double y1
= midi_stream_view()->note_to_y(note
->note());
1331 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1333 cnote
->property_y1() = y1
;
1334 cnote
->property_y2() = y2
;
1336 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1338 const double diamond_size
= update_hit (chit
);
1340 chit
->set_height (diamond_size
);
1346 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1350 double unit_position
= _region
->position () / samples_per_unit
;
1351 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1352 MidiGhostRegion
* ghost
;
1354 if (mtv
&& mtv
->midi_view()) {
1355 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1356 to allow having midi notes on top of note lines and waveforms.
1358 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1360 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1363 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1364 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1365 ghost
->add_note(note
);
1369 ghost
->set_height ();
1370 ghost
->set_duration (_region
->length() / samples_per_unit
);
1371 ghosts
.push_back (ghost
);
1373 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1379 /** Begin tracking note state for successive calls to add_event
1382 MidiRegionView::begin_write()
1384 assert(!_active_notes
);
1385 _active_notes
= new CanvasNote
*[128];
1386 for (unsigned i
=0; i
< 128; ++i
) {
1387 _active_notes
[i
] = 0;
1392 /** Destroy note state for add_event
1395 MidiRegionView::end_write()
1397 delete[] _active_notes
;
1399 _marked_for_selection
.clear();
1400 _marked_for_velocity
.clear();
1404 /** Resolve an active MIDI note (while recording).
1407 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1409 if (midi_view()->note_mode() != Sustained
) {
1413 if (_active_notes
&& _active_notes
[note
]) {
1415 const framepos_t end_time_frames
= beats_to_frames(end_time
);
1417 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1418 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1419 _active_notes
[note
] = 0;
1424 /** Extend active notes to rightmost edge of region (if length is changed)
1427 MidiRegionView::extend_active_notes()
1429 if (!_active_notes
) {
1433 for (unsigned i
=0; i
< 128; ++i
) {
1434 if (_active_notes
[i
]) {
1435 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1442 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1444 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1448 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1450 if (!route_ui
|| !route_ui
->midi_track()) {
1454 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1460 MidiRegionView::play_midi_chord (vector
<boost::shared_ptr
<NoteType
> > notes
)
1462 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1466 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1468 if (!route_ui
|| !route_ui
->midi_track()) {
1472 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1474 for (vector
<boost::shared_ptr
<NoteType
> >::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1483 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1485 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1487 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1488 (note_start_frames
< _region
->start());
1490 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1491 (note
->note() <= midi_stream_view()->highest_note());
1496 /** Update a canvas note's size from its model note.
1497 * @param ev Canvas note to update.
1498 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1501 MidiRegionView::update_note (CanvasNote
* ev
, bool update_ghost_regions
)
1503 boost::shared_ptr
<NoteType
> note
= ev
->note();
1505 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1507 /* trim note display to not overlap the end of its region */
1508 const framepos_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1510 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1511 const double y1
= midi_stream_view()->note_to_y(note
->note());
1512 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1514 ev
->property_x1() = x
;
1515 ev
->property_y1() = y1
;
1517 if (note
->length() > 0) {
1518 ev
->property_x2() = note_endpixel
;
1520 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1523 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1525 if (note
->length() == 0) {
1526 if (_active_notes
) {
1527 assert(note
->note() < 128);
1528 // If this note is already active there's a stuck note,
1529 // finish the old note rectangle
1530 if (_active_notes
[note
->note()]) {
1531 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1532 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1533 old_rect
->property_x2() = x
;
1534 old_rect
->property_outline_what() = (guint32
) 0xF;
1536 _active_notes
[note
->note()] = ev
;
1538 /* outline all but right edge */
1539 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1541 /* outline all edges */
1542 ev
->property_outline_what() = (guint32
) 0xF;
1545 if (update_ghost_regions
) {
1546 for (std::vector
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
1547 MidiGhostRegion
* gr
= dynamic_cast<MidiGhostRegion
*> (*i
);
1549 gr
->update_note (ev
);
1556 MidiRegionView::update_hit (CanvasHit
* ev
)
1558 boost::shared_ptr
<NoteType
> note
= ev
->note();
1560 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1561 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1562 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1563 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1567 return diamond_size
;
1570 /** Add a MIDI note to the view (with length).
1572 * If in sustained mode, notes with length 0 will be considered active
1573 * notes, and resolve_note should be called when the corresponding note off
1574 * event arrives, to properly display the note.
1577 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1579 CanvasNoteEvent
* event
= 0;
1581 assert(note
->time() >= 0);
1582 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1584 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1586 if (midi_view()->note_mode() == Sustained
) {
1588 CanvasNote
* ev_rect
= new CanvasNote(*this, *_note_group
, note
);
1590 update_note (ev_rect
);
1594 MidiGhostRegion
* gr
;
1596 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1597 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1598 gr
->add_note(ev_rect
);
1602 } else if (midi_view()->note_mode() == Percussive
) {
1604 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1606 CanvasHit
* ev_diamond
= new CanvasHit(*this, *_note_group
, diamond_size
, note
);
1608 update_hit (ev_diamond
);
1617 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1618 note_selected(event
, true);
1621 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1622 event
->show_velocity();
1625 event
->on_channel_selection_change(_last_channel_selection
);
1626 _events
.push_back(event
);
1635 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1636 MidiStreamView
* const view
= mtv
->midi_view();
1638 view
->update_note_range(note
->note());
1642 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1643 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1645 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1647 /* potentially extend region to hold new note */
1649 framepos_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1650 framepos_t region_end
= _region
->position() + _region
->length() - 1;
1652 if (end_frame
> region_end
) {
1653 _region
->set_length (end_frame
- _region
->position());
1656 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1657 MidiStreamView
* const view
= mtv
->midi_view();
1659 view
->update_note_range(new_note
->note());
1661 _marked_for_selection
.clear ();
1664 start_note_diff_command (_("step add"));
1665 note_diff_add_note (new_note
, true, false);
1668 // last_step_edit_note = new_note;
1672 MidiRegionView::step_sustain (Evoral::MusicalTime beats
)
1674 change_note_lengths (false, false, beats
, false, true);
1678 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch
, const string
& displaytext
)
1680 assert (patch
->time() >= 0);
1682 const double x
= trackview
.editor().frame_to_pixel (beats_to_frames (patch
->time()));
1684 double const height
= midi_stream_view()->contents_height();
1686 boost::shared_ptr
<CanvasPatchChange
> patch_change
= boost::shared_ptr
<CanvasPatchChange
>(
1687 new CanvasPatchChange(*this, *_note_group
,
1692 _custom_device_mode
,
1696 // Show unless patch change is beyond the region bounds
1697 if (patch
->time() - _region
->start() >= _region
->length() || patch
->time() < _region
->start()) {
1698 patch_change
->hide();
1700 patch_change
->show();
1703 _patch_changes
.push_back (patch_change
);
1707 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1709 MidiModel::PatchChanges::iterator i
= _model
->patch_change_lower_bound (time
);
1710 while (i
!= _model
->patch_changes().end() && (*i
)->channel() != channel
) {
1714 if (i
!= _model
->patch_changes().end()) {
1715 key
.msb
= (*i
)->bank_msb ();
1716 key
.lsb
= (*i
)->bank_lsb ();
1717 key
.program_number
= (*i
)->program ();
1719 key
.msb
= key
.lsb
= key
.program_number
= 0;
1722 assert (key
.is_sane());
1727 MidiRegionView::change_patch_change (CanvasPatchChange
& pc
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1729 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1731 if (pc
.patch()->program() != new_patch
.program_number
) {
1732 c
->change_program (pc
.patch (), new_patch
.program_number
);
1735 int const new_bank
= (new_patch
.msb
<< 7) | new_patch
.lsb
;
1736 if (pc
.patch()->bank() != new_bank
) {
1737 c
->change_bank (pc
.patch (), new_bank
);
1740 _model
->apply_command (*trackview
.session(), c
);
1742 _patch_changes
.clear ();
1743 display_patch_changes ();
1747 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change
, const Evoral::PatchChange
<Evoral::MusicalTime
> & new_change
)
1749 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1751 if (old_change
->time() != new_change
.time()) {
1752 c
->change_time (old_change
, new_change
.time());
1755 if (old_change
->channel() != new_change
.channel()) {
1756 c
->change_channel (old_change
, new_change
.channel());
1759 if (old_change
->program() != new_change
.program()) {
1760 c
->change_program (old_change
, new_change
.program());
1763 if (old_change
->bank() != new_change
.bank()) {
1764 c
->change_bank (old_change
, new_change
.bank());
1767 _model
->apply_command (*trackview
.session(), c
);
1769 _patch_changes
.clear ();
1770 display_patch_changes ();
1773 /** Add a patch change to the region.
1774 * @param t Time in frames relative to region position
1775 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1776 * MidiTimeAxisView::get_channel_for_add())
1779 MidiRegionView::add_patch_change (framecnt_t t
, Evoral::PatchChange
<Evoral::MusicalTime
> const & patch
)
1781 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1783 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("add patch change"));
1784 c
->add (MidiModel::PatchChangePtr (
1785 new Evoral::PatchChange
<Evoral::MusicalTime
> (
1786 frames_to_beats (t
+ midi_region()->start()), mtv
->get_channel_for_add(), patch
.program(), patch
.bank()
1790 _model
->apply_command (*trackview
.session(), c
);
1792 _patch_changes
.clear ();
1793 display_patch_changes ();
1797 MidiRegionView::move_patch_change (CanvasPatchChange
& pc
, Evoral::MusicalTime t
)
1799 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("move patch change"));
1800 c
->change_time (pc
.patch (), t
);
1801 _model
->apply_command (*trackview
.session(), c
);
1803 _patch_changes
.clear ();
1804 display_patch_changes ();
1808 MidiRegionView::delete_patch_change (CanvasPatchChange
* pc
)
1810 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("delete patch change"));
1811 c
->remove (pc
->patch ());
1812 _model
->apply_command (*trackview
.session(), c
);
1814 _patch_changes
.clear ();
1815 display_patch_changes ();
1819 MidiRegionView::previous_patch (CanvasPatchChange
& patch
)
1821 if (patch
.patch()->program() < 127) {
1822 MIDI::Name::PatchPrimaryKey key
;
1823 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1824 key
.program_number
++;
1825 change_patch_change (patch
, key
);
1830 MidiRegionView::next_patch (CanvasPatchChange
& patch
)
1832 if (patch
.patch()->program() > 0) {
1833 MIDI::Name::PatchPrimaryKey key
;
1834 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1835 key
.program_number
--;
1836 change_patch_change (patch
, key
);
1841 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1843 if (_selection
.empty()) {
1847 _selection
.erase (cne
);
1851 MidiRegionView::delete_selection()
1853 if (_selection
.empty()) {
1857 start_note_diff_command (_("delete selection"));
1859 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1860 if ((*i
)->selected()) {
1861 _note_diff_command
->remove((*i
)->note());
1871 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1873 start_note_diff_command (_("delete note"));
1874 _note_diff_command
->remove (n
);
1877 trackview
.editor().verbose_cursor()->hide ();
1881 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1883 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1884 if ((*i
)->selected() && (*i
) != ev
) {
1885 (*i
)->set_selected(false);
1886 (*i
)->hide_velocity();
1894 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1896 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1899 Selection::iterator tmp
= i
;
1902 (*i
)->set_selected (false);
1903 _selection
.erase (i
);
1912 /* don't bother with removing this regionview from the editor selection,
1913 since we're about to add another note, and thus put/keep this
1914 regionview in the editor selection.
1917 if (!ev
->selected()) {
1918 add_to_selection (ev
);
1923 MidiRegionView::select_all_notes ()
1927 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1928 add_to_selection (*i
);
1933 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1935 uint8_t low_note
= 127;
1936 uint8_t high_note
= 0;
1937 MidiModel::Notes
& notes (_model
->notes());
1938 _optimization_iterator
= _events
.begin();
1944 if (extend
&& _selection
.empty()) {
1950 /* scan existing selection to get note range */
1952 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1953 if ((*i
)->note()->note() < low_note
) {
1954 low_note
= (*i
)->note()->note();
1956 if ((*i
)->note()->note() > high_note
) {
1957 high_note
= (*i
)->note()->note();
1961 low_note
= min (low_note
, notenum
);
1962 high_note
= max (high_note
, notenum
);
1965 _no_sound_notes
= true;
1967 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1969 boost::shared_ptr
<NoteType
> note (*n
);
1970 CanvasNoteEvent
* cne
;
1971 bool select
= false;
1973 if (((1 << note
->channel()) & channel_mask
) != 0) {
1975 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1978 } else if (note
->note() == notenum
) {
1984 if ((cne
= find_canvas_note (note
)) != 0) {
1985 // extend is false because we've taken care of it,
1986 // since it extends by time range, not pitch.
1987 note_selected (cne
, add
, false);
1991 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1995 _no_sound_notes
= false;
1999 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
2001 MidiModel::Notes
& notes (_model
->notes());
2002 _optimization_iterator
= _events
.begin();
2004 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
2006 boost::shared_ptr
<NoteType
> note (*n
);
2007 CanvasNoteEvent
* cne
;
2009 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
2010 if ((cne
= find_canvas_note (note
)) != 0) {
2011 if (cne
->selected()) {
2012 note_deselected (cne
);
2014 note_selected (cne
, true, false);
2022 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
2025 clear_selection_except(ev
);
2030 if (!ev
->selected()) {
2031 add_to_selection (ev
);
2035 /* find end of latest note selected, select all between that and the start of "ev" */
2037 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2038 Evoral::MusicalTime latest
= 0;
2040 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2041 if ((*i
)->note()->end_time() > latest
) {
2042 latest
= (*i
)->note()->end_time();
2044 if ((*i
)->note()->time() < earliest
) {
2045 earliest
= (*i
)->note()->time();
2049 if (ev
->note()->end_time() > latest
) {
2050 latest
= ev
->note()->end_time();
2053 if (ev
->note()->time() < earliest
) {
2054 earliest
= ev
->note()->time();
2057 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2059 /* find notes entirely within OR spanning the earliest..latest range */
2061 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
2062 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
2063 add_to_selection (*i
);
2067 /* if events were guaranteed to be time sorted, we could do this.
2068 but as of sept 10th 2009, they no longer are.
2071 if ((*i
)->note()->time() > latest
) {
2080 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
2082 remove_from_selection (ev
);
2086 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
2096 // TODO: Make this faster by storing the last updated selection rect, and only
2097 // adjusting things that are in the area that appears/disappeared.
2098 // We probably need a tree to be able to find events in O(log(n)) time.
2100 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2102 /* check if any corner of the note is inside the rect
2105 1) this is computing "touched by", not "contained by" the rect.
2106 2) this does not require that events be sorted in time.
2109 const double ix1
= (*i
)->x1();
2110 const double ix2
= (*i
)->x2();
2111 const double iy1
= (*i
)->y1();
2112 const double iy2
= (*i
)->y2();
2114 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2115 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
2116 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2117 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
2120 if (!(*i
)->selected()) {
2121 add_to_selection (*i
);
2123 } else if ((*i
)->selected()) {
2124 // Not inside rectangle
2125 remove_from_selection (*i
);
2131 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
2133 Selection::iterator i
= _selection
.find (ev
);
2135 if (i
!= _selection
.end()) {
2136 _selection
.erase (i
);
2139 ev
->set_selected (false);
2140 ev
->hide_velocity ();
2142 if (_selection
.empty()) {
2143 PublicEditor
& editor (trackview
.editor());
2144 editor
.get_selection().remove (this);
2149 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
2151 bool add_mrv_selection
= false;
2153 if (_selection
.empty()) {
2154 add_mrv_selection
= true;
2157 if (_selection
.insert (ev
).second
) {
2158 ev
->set_selected (true);
2159 play_midi_note ((ev
)->note());
2162 if (add_mrv_selection
) {
2163 PublicEditor
& editor (trackview
.editor());
2164 editor
.get_selection().add (this);
2169 MidiRegionView::move_selection(double dx
, double dy
, double cumulative_dy
)
2171 typedef vector
<boost::shared_ptr
<NoteType
> > PossibleChord
;
2172 PossibleChord to_play
;
2173 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2175 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2176 if ((*i
)->note()->time() < earliest
) {
2177 earliest
= (*i
)->note()->time();
2181 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2182 if (Evoral::musical_time_equal ((*i
)->note()->time(), earliest
)) {
2183 to_play
.push_back ((*i
)->note());
2185 (*i
)->move_event(dx
, dy
);
2188 if (dy
&& !_selection
.empty() && !_no_sound_notes
&& trackview
.editor().sound_notes()) {
2190 if (to_play
.size() > 1) {
2192 PossibleChord shifted
;
2194 for (PossibleChord::iterator n
= to_play
.begin(); n
!= to_play
.end(); ++n
) {
2195 boost::shared_ptr
<NoteType
> moved_note (new NoteType (**n
));
2196 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2197 shifted
.push_back (moved_note
);
2200 play_midi_chord (shifted
);
2202 } else if (!to_play
.empty()) {
2204 boost::shared_ptr
<NoteType
> moved_note (new NoteType (*to_play
.front()));
2205 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2206 play_midi_note (moved_note
);
2212 MidiRegionView::note_dropped(CanvasNoteEvent
*, frameoffset_t dt
, int8_t dnote
)
2214 assert (!_selection
.empty());
2216 uint8_t lowest_note_in_selection
= 127;
2217 uint8_t highest_note_in_selection
= 0;
2218 uint8_t highest_note_difference
= 0;
2220 // find highest and lowest notes first
2222 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2223 uint8_t pitch
= (*i
)->note()->note();
2224 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
2225 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
2229 cerr << "dnote: " << (int) dnote << endl;
2230 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2231 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2232 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2233 << int(highest_note_in_selection) << endl;
2234 cerr << "selection size: " << _selection.size() << endl;
2235 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2238 // Make sure the note pitch does not exceed the MIDI standard range
2239 if (highest_note_in_selection
+ dnote
> 127) {
2240 highest_note_difference
= highest_note_in_selection
- 127;
2243 start_note_diff_command (_("move notes"));
2245 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
2247 Evoral::MusicalTime new_time
= frames_to_beats (beats_to_frames ((*i
)->note()->time()) + dt
);
2253 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2255 uint8_t original_pitch
= (*i
)->note()->note();
2256 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
2258 // keep notes in standard midi range
2259 clamp_to_0_127(new_pitch
);
2261 // keep original pitch if note is dragged outside valid midi range
2262 if ((original_pitch
!= 0 && new_pitch
== 0)
2263 || (original_pitch
!= 127 && new_pitch
== 127)) {
2264 new_pitch
= original_pitch
;
2267 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2268 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2270 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::NoteNumber
, new_pitch
);
2275 // care about notes being moved beyond the upper/lower bounds on the canvas
2276 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2277 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2278 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2283 MidiRegionView::snap_pixel_to_frame(double x
)
2285 PublicEditor
& editor (trackview
.editor());
2286 return snap_frame_to_frame (editor
.pixel_to_frame (x
));
2289 /** Snap a frame offset within our region using the current snap settings.
2290 * @param x Frame offset from this region's position.
2291 * @return Snapped frame offset from this region's position.
2294 MidiRegionView::snap_frame_to_frame (frameoffset_t x
)
2296 PublicEditor
& editor
= trackview
.editor();
2298 /* x is region relative, convert it to global absolute frames */
2299 framepos_t
const session_frame
= x
+ _region
->position();
2301 /* try a snap in either direction */
2302 framepos_t frame
= session_frame
;
2303 editor
.snap_to (frame
, 0);
2305 /* if we went off the beginning of the region, snap forwards */
2306 if (frame
< _region
->position ()) {
2307 frame
= session_frame
;
2308 editor
.snap_to (frame
, 1);
2311 /* back to region relative */
2312 return frame
- _region
->position();
2316 MidiRegionView::snap_to_pixel(double x
)
2318 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2322 MidiRegionView::get_position_pixels()
2324 framepos_t region_frame
= get_position();
2325 return trackview
.editor().frame_to_pixel(region_frame
);
2329 MidiRegionView::get_end_position_pixels()
2331 framepos_t frame
= get_position() + get_duration ();
2332 return trackview
.editor().frame_to_pixel(frame
);
2336 MidiRegionView::beats_to_frames(double beats
) const
2338 return _time_converter
.to(beats
);
2342 MidiRegionView::frames_to_beats(framepos_t frames
) const
2344 return _time_converter
.from(frames
);
2348 MidiRegionView::begin_resizing (bool /*at_front*/)
2350 _resize_data
.clear();
2352 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2353 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2355 // only insert CanvasNotes into the map
2357 NoteResizeData
*resize_data
= new NoteResizeData();
2358 resize_data
->canvas_note
= note
;
2360 // create a new SimpleRect from the note which will be the resize preview
2361 SimpleRect
*resize_rect
= new SimpleRect(
2362 *_note_group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2364 // calculate the colors: get the color settings
2365 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2366 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2369 // make the resize preview notes more transparent and bright
2370 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2372 // calculate color based on note velocity
2373 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2374 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2378 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2379 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2381 resize_data
->resize_rect
= resize_rect
;
2382 _resize_data
.push_back(resize_data
);
2387 /** Update resizing notes while user drags.
2388 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2389 * @param at_front which end of the note (true == note on, false == note off)
2390 * @param delta_x change in mouse position since the start of the drag
2391 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2392 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2393 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2394 * as the \a primary note.
2397 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2399 bool cursor_set
= false;
2401 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2402 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2403 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2408 current_x
= canvas_note
->x1() + delta_x
;
2410 current_x
= primary
->x1() + delta_x
;
2414 current_x
= canvas_note
->x2() + delta_x
;
2416 current_x
= primary
->x2() + delta_x
;
2421 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2422 resize_rect
->property_x2() = canvas_note
->x2();
2424 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2425 resize_rect
->property_x1() = canvas_note
->x1();
2431 beats
= snap_pixel_to_frame (current_x
);
2432 beats
= frames_to_beats (beats
);
2437 if (beats
< canvas_note
->note()->end_time()) {
2438 len
= canvas_note
->note()->time() - beats
;
2439 len
+= canvas_note
->note()->length();
2444 if (beats
>= canvas_note
->note()->time()) {
2445 len
= beats
- canvas_note
->note()->time();
2452 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2453 show_verbose_cursor (buf
, 0, 0);
2462 /** Finish resizing notes when the user releases the mouse button.
2463 * Parameters the same as for \a update_resizing().
2466 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2468 start_note_diff_command (_("resize notes"));
2470 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2471 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2472 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
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 current_x
= snap_pixel_to_frame (current_x
);
2490 current_x
= frames_to_beats (current_x
);
2492 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2493 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::StartTime
, current_x
);
2495 double len
= canvas_note
->note()->time() - current_x
;
2496 len
+= canvas_note
->note()->length();
2499 /* XXX convert to beats */
2500 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2505 double len
= current_x
- canvas_note
->note()->time();
2508 /* XXX convert to beats */
2509 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2517 _resize_data
.clear();
2522 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2524 uint8_t new_velocity
;
2527 new_velocity
= event
->note()->velocity() + velocity
;
2528 clamp_to_0_127(new_velocity
);
2530 new_velocity
= velocity
;
2533 event
->set_selected (event
->selected()); // change color
2535 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Velocity
, new_velocity
);
2539 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2544 new_note
= event
->note()->note() + note
;
2549 clamp_to_0_127 (new_note
);
2550 note_diff_add_change (event
, MidiModel::NoteDiffCommand::NoteNumber
, new_note
);
2554 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2556 bool change_start
= false;
2557 bool change_length
= false;
2558 Evoral::MusicalTime new_start
= 0;
2559 Evoral::MusicalTime new_length
= 0;
2561 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2563 front_delta: if positive - move the start of the note later in time (shortening it)
2564 if negative - move the start of the note earlier in time (lengthening it)
2566 end_delta: if positive - move the end of the note later in time (lengthening it)
2567 if negative - move the end of the note earlier in time (shortening it)
2571 if (front_delta
< 0) {
2573 if (event
->note()->time() < -front_delta
) {
2576 new_start
= event
->note()->time() + front_delta
; // moves earlier
2579 /* start moved toward zero, so move the end point out to where it used to be.
2580 Note that front_delta is negative, so this increases the length.
2583 new_length
= event
->note()->length() - front_delta
;
2584 change_start
= true;
2585 change_length
= true;
2589 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2591 if (new_pos
< event
->note()->end_time()) {
2592 new_start
= event
->note()->time() + front_delta
;
2593 /* start moved toward the end, so move the end point back to where it used to be */
2594 new_length
= event
->note()->length() - front_delta
;
2595 change_start
= true;
2596 change_length
= true;
2603 bool can_change
= true;
2604 if (end_delta
< 0) {
2605 if (event
->note()->length() < -end_delta
) {
2611 new_length
= event
->note()->length() + end_delta
;
2612 change_length
= true;
2617 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_start
);
2620 if (change_length
) {
2621 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, new_length
);
2626 MidiRegionView::change_note_channel (CanvasNoteEvent
* event
, int8_t chn
, bool relative
)
2628 uint8_t new_channel
;
2632 if (event
->note()->channel() < -chn
) {
2635 new_channel
= event
->note()->channel() + chn
;
2638 new_channel
= event
->note()->channel() + chn
;
2641 new_channel
= (uint8_t) chn
;
2644 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Channel
, new_channel
);
2648 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2650 Evoral::MusicalTime new_time
;
2654 if (event
->note()->time() < -delta
) {
2657 new_time
= event
->note()->time() + delta
;
2660 new_time
= event
->note()->time() + delta
;
2666 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2670 MidiRegionView::change_note_length (CanvasNoteEvent
* event
, Evoral::MusicalTime t
)
2672 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, t
);
2676 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2680 if (_selection
.empty()) {
2695 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2696 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2702 start_note_diff_command (_("change velocities"));
2704 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2705 Selection::iterator next
= i
;
2707 change_note_velocity (*i
, delta
, true);
2713 if (!_selection
.empty()) {
2715 snprintf (buf
, sizeof (buf
), "Vel %d",
2716 (int) (*_selection
.begin())->note()->velocity());
2717 show_verbose_cursor (buf
, 10, 10);
2723 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2725 if (_selection
.empty()) {
2742 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2744 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2748 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2755 start_note_diff_command (_("transpose"));
2757 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2758 Selection::iterator next
= i
;
2760 change_note_note (*i
, delta
, true);
2768 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, Evoral::MusicalTime delta
, bool start
, bool end
)
2774 /* grab the current grid distance */
2776 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2778 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2779 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2789 start_note_diff_command (_("change note lengths"));
2791 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2792 Selection::iterator next
= i
;
2795 /* note the negation of the delta for start */
2797 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2806 MidiRegionView::nudge_notes (bool forward
)
2808 if (_selection
.empty()) {
2812 /* pick a note as the point along the timeline to get the nudge distance.
2813 its not necessarily the earliest note, so we may want to pull the notes out
2814 into a vector and sort before using the first one.
2817 framepos_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2819 framepos_t distance
;
2821 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2823 /* grid is off - use nudge distance */
2825 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2831 framepos_t next_pos
= ref_point
;
2834 if (max_framepos
- 1 < next_pos
) {
2838 if (next_pos
== 0) {
2844 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2845 distance
= ref_point
- next_pos
;
2848 if (distance
== 0) {
2852 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2858 start_note_diff_command (_("nudge"));
2860 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2861 Selection::iterator next
= i
;
2863 change_note_time (*i
, delta
, true);
2871 MidiRegionView::change_channel(uint8_t channel
)
2873 start_note_diff_command(_("change channel"));
2874 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2875 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::Channel
, channel
);
2883 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2885 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2887 _pre_enter_cursor
= editor
->get_canvas_cursor ();
2889 if (_mouse_state
== SelectTouchDragging
) {
2890 note_selected (ev
, true);
2893 show_verbose_cursor (ev
->note ());
2897 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2899 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2901 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2902 (*i
)->hide_velocity ();
2905 editor
->verbose_cursor()->hide ();
2907 if (_pre_enter_cursor
) {
2908 editor
->set_canvas_cursor (_pre_enter_cursor
);
2909 _pre_enter_cursor
= 0;
2914 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange
* ev
)
2917 s
<< ((int) ev
->patch()->program() + 1) << ":" << (ev
->patch()->bank() + 1);
2918 show_verbose_cursor (s
.str(), 10, 20);
2922 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange
*)
2924 trackview
.editor().verbose_cursor()->hide ();
2928 MidiRegionView::note_mouse_position (float x_fraction
, float /*y_fraction*/, bool can_set_cursor
)
2930 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2932 if (x_fraction
> 0.0 && x_fraction
< 0.25) {
2933 editor
->set_canvas_cursor (editor
->cursors()->left_side_trim
);
2934 } else if (x_fraction
>= 0.75 && x_fraction
< 1.0) {
2935 editor
->set_canvas_cursor (editor
->cursors()->right_side_trim
);
2937 if (_pre_enter_cursor
&& can_set_cursor
) {
2938 editor
->set_canvas_cursor (_pre_enter_cursor
);
2944 MidiRegionView::set_frame_color()
2948 TimeAxisViewItem::set_frame_color ();
2955 f
= ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2956 } else if (high_enough_for_name
) {
2957 f
= ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2962 if (!rect_visible
) {
2963 f
= UINT_RGBA_CHANGE_A (f
, 0);
2966 frame
->property_fill_color_rgba() = f
;
2970 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2974 case FilterChannels
:
2975 _force_channel
= -1;
2978 _force_channel
= mask
;
2979 mask
= 0xFFFF; // Show all notes as active (below)
2982 // Update notes for selection
2983 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2984 (*i
)->on_channel_selection_change(mask
);
2987 _last_channel_selection
= mask
;
2989 _patch_changes
.clear ();
2990 display_patch_changes ();
2994 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2996 _model_name
= model
;
2997 _custom_device_mode
= custom_device_mode
;
3002 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
3004 if (_selection
.empty()) {
3008 PublicEditor
& editor (trackview
.editor());
3012 /* XXX what to do ? */
3016 editor
.get_cut_buffer().add (selection_as_cut_buffer());
3024 start_note_diff_command();
3026 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3033 note_diff_remove_note (*i
);
3043 MidiRegionView::selection_as_cut_buffer () const
3047 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3048 NoteType
* n
= (*i
)->note().get();
3049 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
3052 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
3058 /** This method handles undo */
3060 MidiRegionView::paste (framepos_t pos
, float times
, const MidiCutBuffer
& mcb
)
3066 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("MIDI paste @ %1 times %2\n", pos
, times
));
3068 trackview
.session()->begin_reversible_command (_("paste"));
3070 start_note_diff_command (_("paste"));
3072 Evoral::MusicalTime beat_delta
;
3073 Evoral::MusicalTime paste_pos_beats
;
3074 Evoral::MusicalTime duration
;
3075 Evoral::MusicalTime end_point
= 0;
3077 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
3078 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
3079 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
3080 paste_pos_beats
= 0;
3082 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",
3083 (*mcb
.notes().begin())->time(),
3084 (*mcb
.notes().rbegin())->end_time(),
3085 duration
, pos
, _region
->position(),
3086 paste_pos_beats
, beat_delta
));
3090 for (int n
= 0; n
< (int) times
; ++n
) {
3092 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
3094 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
3095 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
3097 /* make all newly added notes selected */
3099 note_diff_add_note (copied_note
, true);
3100 end_point
= copied_note
->end_time();
3103 paste_pos_beats
+= duration
;
3106 /* if we pasted past the current end of the region, extend the region */
3108 framepos_t end_frame
= _region
->position() + beats_to_frames (end_point
);
3109 framepos_t region_end
= _region
->position() + _region
->length() - 1;
3111 if (end_frame
> region_end
) {
3113 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste extended region from %1 to %2\n", region_end
, end_frame
));
3115 _region
->clear_changes ();
3116 _region
->set_length (end_frame
);
3117 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
3122 trackview
.session()->commit_reversible_command ();
3125 struct EventNoteTimeEarlyFirstComparator
{
3126 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
3127 return a
->note()->time() < b
->note()->time();
3132 MidiRegionView::time_sort_events ()
3134 if (!_sort_needed
) {
3138 EventNoteTimeEarlyFirstComparator cmp
;
3141 _sort_needed
= false;
3145 MidiRegionView::goto_next_note ()
3147 // framepos_t pos = -1;
3148 bool use_next
= false;
3150 if (_events
.back()->selected()) {
3154 time_sort_events ();
3156 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3157 if ((*i
)->selected()) {
3160 } else if (use_next
) {
3162 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3167 /* use the first one */
3169 unique_select (_events
.front());
3174 MidiRegionView::goto_previous_note ()
3176 // framepos_t pos = -1;
3177 bool use_next
= false;
3179 if (_events
.front()->selected()) {
3183 time_sort_events ();
3185 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
3186 if ((*i
)->selected()) {
3189 } else if (use_next
) {
3191 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3196 /* use the last one */
3198 unique_select (*(_events
.rbegin()));
3202 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
3204 bool had_selected
= false;
3206 time_sort_events ();
3208 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3209 if ((*i
)->selected()) {
3210 selected
.insert ((*i
)->note());
3211 had_selected
= true;
3215 if (allow_all_if_none_selected
&& !had_selected
) {
3216 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3217 selected
.insert ((*i
)->note());
3223 MidiRegionView::update_ghost_note (double x
, double y
)
3225 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3230 _note_group
->w2i (x
, y
);
3231 framepos_t
const f
= snap_pixel_to_frame (x
);
3234 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
3240 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
3242 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
3243 _ghost_note
->note()->set_length (length
);
3244 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
3245 _ghost_note
->note()->set_channel (mtv
->get_channel_for_add ());
3247 /* the ghost note does not appear in ghost regions, so pass false in here */
3248 update_note (_ghost_note
, false);
3250 show_verbose_cursor (_ghost_note
->note ());
3254 MidiRegionView::create_ghost_note (double x
, double y
)
3259 boost::shared_ptr
<NoteType
> g (new NoteType
);
3260 _ghost_note
= new NoEventCanvasNote (*this, *_note_group
, g
);
3261 _ghost_note
->property_outline_color_rgba() = 0x000000aa;
3262 update_ghost_note (x
, y
);
3263 _ghost_note
->show ();
3268 show_verbose_cursor (_ghost_note
->note ());
3272 MidiRegionView::snap_changed ()
3278 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
3282 MidiRegionView::drop_down_keys ()
3284 _mouse_state
= None
;
3288 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double /*x*/, double y
)
3290 double note
= midi_stream_view()->y_to_note(y
);
3292 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3294 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
3296 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
3297 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3298 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
3299 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3304 bool add_mrv_selection
= false;
3306 if (_selection
.empty()) {
3307 add_mrv_selection
= true;
3310 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
3311 if (_selection
.insert (*i
).second
) {
3312 (*i
)->set_selected (true);
3316 if (add_mrv_selection
) {
3317 PublicEditor
& editor (trackview
.editor());
3318 editor
.get_selection().add (this);
3323 MidiRegionView::color_handler ()
3325 RegionView::color_handler ();
3327 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3328 (*i
)->set_selected ((*i
)->selected()); // will change color
3331 /* XXX probably more to do here */
3335 MidiRegionView::enable_display (bool yn
)
3337 RegionView::enable_display (yn
);
3344 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos
)
3346 if (_step_edit_cursor
== 0) {
3347 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
3349 _step_edit_cursor
= new ArdourCanvas::SimpleRect (*group
);
3350 _step_edit_cursor
->property_y1() = 0;
3351 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
3352 _step_edit_cursor
->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3353 _step_edit_cursor
->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3356 move_step_edit_cursor (pos
);
3357 _step_edit_cursor
->show ();
3361 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos
)
3363 _step_edit_cursor_position
= pos
;
3365 if (_step_edit_cursor
) {
3366 double pixel
= trackview
.editor().frame_to_pixel (beats_to_frames (pos
));
3367 _step_edit_cursor
->property_x1() = pixel
;
3368 set_step_edit_cursor_width (_step_edit_cursor_width
);
3373 MidiRegionView::hide_step_edit_cursor ()
3375 if (_step_edit_cursor
) {
3376 _step_edit_cursor
->hide ();
3381 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats
)
3383 _step_edit_cursor_width
= beats
;
3385 if (_step_edit_cursor
) {
3386 _step_edit_cursor
->property_x2() = _step_edit_cursor
->property_x1() + trackview
.editor().frame_to_pixel (beats_to_frames (beats
));
3390 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3391 * @param buf Data that has been recorded.
3392 * @param w Source that this data will end up in.
3395 MidiRegionView::data_recorded (boost::shared_ptr
<MidiBuffer
> buf
, boost::weak_ptr
<MidiSource
> w
)
3397 if (!_active_notes
) {
3398 /* we aren't actively being recorded to */
3402 boost::shared_ptr
<MidiSource
> src
= w
.lock ();
3403 if (!src
|| src
!= midi_region()->midi_source()) {
3404 /* recorded data was not destined for our source */
3408 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*> (&trackview
);
3409 BeatsFramesConverter
converter (trackview
.session()->tempo_map(), mtv
->midi_track()->get_capture_start_frame (0));
3411 framepos_t back
= max_framepos
;
3413 for (MidiBuffer::iterator i
= buf
->begin(); i
!= buf
->end(); ++i
) {
3414 Evoral::MIDIEvent
<MidiBuffer::TimeType
> const ev (*i
, false);
3415 assert (ev
.buffer ());
3417 Evoral::MusicalTime
const time_beats
= converter
.from (ev
.time () - converter
.origin_b ());
3419 if (ev
.type() == MIDI_CMD_NOTE_ON
) {
3421 boost::shared_ptr
<NoteType
> note (
3422 new NoteType (ev
.channel(), time_beats
, 0, ev
.note(), ev
.velocity())
3425 add_note (note
, true);
3427 /* fix up our note range */
3428 if (ev
.note() < _current_range_min
) {
3429 midi_stream_view()->apply_note_range (ev
.note(), _current_range_max
, true);
3430 } else if (ev
.note() > _current_range_max
) {
3431 midi_stream_view()->apply_note_range (_current_range_min
, ev
.note(), true);
3434 } else if (ev
.type() == MIDI_CMD_NOTE_OFF
) {
3435 resolve_note (ev
.note (), time_beats
);
3441 midi_stream_view()->check_record_layers (region(), back
);
3445 MidiRegionView::trim_front_starting ()
3447 /* Reparent the note group to the region view's parent, so that it doesn't change
3448 when the region view is trimmed.
3450 _temporary_note_group
= new ArdourCanvas::Group (*group
->property_parent ());
3451 _temporary_note_group
->move (group
->property_x(), group
->property_y());
3452 _note_group
->reparent (*_temporary_note_group
);
3456 MidiRegionView::trim_front_ending ()
3458 _note_group
->reparent (*group
);
3459 delete _temporary_note_group
;
3460 _temporary_note_group
= 0;
3462 if (_region
->start() < 0) {
3463 /* Trim drag made start time -ve; fix this */
3464 midi_region()->fix_negative_start ();
3469 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange
* pc
)
3471 PatchChangeDialog
d (&_time_converter
, trackview
.session(), *pc
->patch (), Gtk::Stock::APPLY
);
3472 if (d
.run () != Gtk::RESPONSE_ACCEPT
) {
3476 change_patch_change (pc
->patch(), d
.patch ());
3481 MidiRegionView::show_verbose_cursor (boost::shared_ptr
<NoteType
> n
) const
3484 snprintf (buf
, sizeof (buf
), "%s (%d) Chn %d\nVel %d",
3485 Evoral::midi_note_name (n
->note()).c_str(),
3487 (int) n
->channel() + 1,
3488 (int) n
->velocity());
3490 show_verbose_cursor (buf
, 10, 20);
3494 MidiRegionView::show_verbose_cursor (string
const & text
, double xoffset
, double yoffset
) const
3498 trackview
.editor().get_pointer_position (wx
, wy
);
3503 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3505 double x1
, y1
, x2
, y2
;
3506 trackview
.editor().verbose_cursor()->canvas_item()->get_bounds (x1
, y1
, x2
, y2
);
3508 if ((wy
+ y2
- y1
) > trackview
.editor().canvas_height()) {
3509 wy
-= (y2
- y1
) + 2 * yoffset
;
3512 trackview
.editor().verbose_cursor()->set (text
, wx
, wy
);
3513 trackview
.editor().verbose_cursor()->show ();