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_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
727 goto_previous_note ();
733 } else if (ev
->keyval
== GDK_Up
) {
735 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
736 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
738 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
739 change_velocities (true, fine
, allow_smush
);
741 transpose (true, fine
, allow_smush
);
745 } else if (ev
->keyval
== GDK_Down
) {
747 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
748 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
750 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
751 change_velocities (false, fine
, allow_smush
);
753 transpose (false, fine
, allow_smush
);
757 } else if (ev
->keyval
== GDK_Left
) {
762 } else if (ev
->keyval
== GDK_Right
) {
767 } else if (ev
->keyval
== GDK_Control_L
) {
770 } else if (ev
->keyval
== GDK_c
) {
779 MidiRegionView::key_release (GdkEventKey
* ev
)
781 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
789 MidiRegionView::channel_edit ()
792 uint8_t current_channel
;
794 if (_selection
.empty()) {
798 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
800 current_channel
= (*i
)->note()->channel ();
805 MidiChannelDialog
channel_dialog (current_channel
);
806 int ret
= channel_dialog
.run ();
809 case Gtk::RESPONSE_OK
:
815 uint8_t new_channel
= channel_dialog
.active_channel ();
817 start_note_diff_command (_("channel edit"));
819 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
820 Selection::iterator next
= i
;
822 change_note_channel (*i
, new_channel
);
830 MidiRegionView::show_list_editor ()
833 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
835 _list_editor
->present ();
838 /** Add a note to the model, and the view, at a canvas (click) coordinate.
839 * \param x horizontal position in pixels
840 * \param y vertical position in pixels
841 * \param length duration of the note in beats, which will be snapped to the grid
842 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
845 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
847 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
848 MidiStreamView
* const view
= mtv
->midi_view();
850 double note
= view
->y_to_note(y
);
853 assert(note
<= 127.0);
855 // Start of note in frames relative to region start
856 framepos_t
const start_frames
= snap_pixel_to_frame (x
);
857 assert(start_frames
>= 0);
860 length
= frames_to_beats(
861 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
863 assert (length
!= 0);
866 length
= frames_to_beats (beats_to_frames (length
) - 1);
869 const boost::shared_ptr
<NoteType
> new_note (new NoteType (mtv
->get_channel_for_add (),
870 frames_to_beats(start_frames
+ _region
->start()), length
,
871 (uint8_t)note
, 0x40));
873 if (_model
->contains (new_note
)) {
877 view
->update_note_range(new_note
->note());
879 MidiModel::NoteDiffCommand
* cmd
= _model
->new_note_diff_command("add note");
881 _model
->apply_command(*trackview
.session(), cmd
);
883 play_midi_note (new_note
);
887 MidiRegionView::clear_events()
892 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
893 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
898 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
903 _patch_changes
.clear();
905 _optimization_iterator
= _events
.end();
909 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
913 content_connection
.disconnect ();
914 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
918 if (_enable_display
) {
924 MidiRegionView::start_note_diff_command (string name
)
926 if (!_note_diff_command
) {
927 _note_diff_command
= _model
->new_note_diff_command (name
);
932 MidiRegionView::note_diff_add_note (const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
934 if (_note_diff_command
) {
935 _note_diff_command
->add (note
);
938 _marked_for_selection
.insert(note
);
941 _marked_for_velocity
.insert(note
);
946 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent
* ev
)
948 if (_note_diff_command
&& ev
->note()) {
949 _note_diff_command
->remove(ev
->note());
954 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
955 MidiModel::NoteDiffCommand::Property property
,
958 if (_note_diff_command
) {
959 _note_diff_command
->change (ev
->note(), property
, val
);
964 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
965 MidiModel::NoteDiffCommand::Property property
,
966 Evoral::MusicalTime val
)
968 if (_note_diff_command
) {
969 _note_diff_command
->change (ev
->note(), property
, val
);
974 MidiRegionView::apply_diff (bool as_subcommand
)
978 if (!_note_diff_command
) {
982 if ((add_or_remove
= _note_diff_command
->adds_or_removes())) {
983 // Mark all selected notes for selection when model reloads
984 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
985 _marked_for_selection
.insert((*i
)->note());
990 _model
->apply_command_as_subcommand (*trackview
.session(), _note_diff_command
);
992 _model
->apply_command (*trackview
.session(), _note_diff_command
);
995 _note_diff_command
= 0;
996 midi_view()->midi_track()->playlist_modified();
999 _marked_for_selection
.clear();
1002 _marked_for_velocity
.clear();
1006 MidiRegionView::abort_command()
1008 delete _note_diff_command
;
1009 _note_diff_command
= 0;
1014 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
1016 if (_optimization_iterator
!= _events
.end()) {
1017 ++_optimization_iterator
;
1020 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
1021 return *_optimization_iterator
;
1024 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
1025 if ((*_optimization_iterator
)->note() == note
) {
1026 return *_optimization_iterator
;
1034 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
1036 MidiModel::Notes notes
;
1037 _model
->get_notes (notes
, op
, val
, chan_mask
);
1039 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1040 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
1048 MidiRegionView::redisplay_model()
1050 // Don't redisplay the model if we're currently recording and displaying that
1051 if (_active_notes
) {
1059 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1060 (*i
)->invalidate ();
1063 MidiModel::ReadLock
lock(_model
->read_lock());
1065 MidiModel::Notes
& notes (_model
->notes());
1066 _optimization_iterator
= _events
.begin();
1068 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1070 boost::shared_ptr
<NoteType
> note (*n
);
1071 CanvasNoteEvent
* cne
;
1074 if (note_in_region_range (note
, visible
)) {
1076 if ((cne
= find_canvas_note (note
)) != 0) {
1083 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
1085 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
1097 add_note (note
, visible
);
1102 if ((cne
= find_canvas_note (note
)) != 0) {
1110 /* remove note items that are no longer valid */
1112 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
1113 if (!(*i
)->valid ()) {
1115 i
= _events
.erase (i
);
1121 _patch_changes
.clear();
1125 display_patch_changes ();
1127 _marked_for_selection
.clear ();
1128 _marked_for_velocity
.clear ();
1130 /* we may have caused _events to contain things out of order (e.g. if a note
1131 moved earlier or later). we don't generally need them in time order, but
1132 make a note that a sort is required for those cases that require it.
1135 _sort_needed
= true;
1139 MidiRegionView::display_patch_changes ()
1141 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1142 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
1144 for (uint8_t i
= 0; i
< 16; ++i
) {
1145 if (chn_mask
& (1<<i
)) {
1146 display_patch_changes_on_channel (i
);
1152 MidiRegionView::display_patch_changes_on_channel (uint8_t channel
)
1154 for (MidiModel::PatchChanges::const_iterator i
= _model
->patch_changes().begin(); i
!= _model
->patch_changes().end(); ++i
) {
1156 if ((*i
)->channel() != channel
) {
1160 MIDI::Name::PatchPrimaryKey
patch_key ((*i
)->bank_msb (), (*i
)->bank_lsb (), (*i
)->program ());
1162 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1163 MIDI::Name::MidiPatchManager::instance().find_patch(
1164 _model_name
, _custom_device_mode
, channel
, patch_key
);
1167 add_canvas_patch_change (*i
, patch
->name());
1170 /* program and bank numbers are zero-based: convert to one-based */
1171 snprintf (buf
, 16, "%d\n%d", (*i
)->program() + 1, (*i
)->bank() + 1);
1172 add_canvas_patch_change (*i
, buf
);
1178 MidiRegionView::display_sysexes()
1180 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1181 Evoral::MusicalTime time
= (*i
)->time();
1186 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1187 str
<< int((*i
)->buffer()[b
]);
1188 if (b
!= (*i
)->size() -1) {
1192 string text
= str
.str();
1194 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1196 double height
= midi_stream_view()->contents_height();
1198 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1199 new CanvasSysEx(*this, *_note_group
, text
, height
, x
, 1.0));
1201 // Show unless patch change is beyond the region bounds
1202 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1208 _sys_exes
.push_back(sysex
);
1213 MidiRegionView::~MidiRegionView ()
1215 in_destructor
= true;
1217 trackview
.editor().verbose_cursor()->hide ();
1219 note_delete_connection
.disconnect ();
1221 delete _list_editor
;
1223 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1225 if (_active_notes
) {
1233 delete _note_diff_command
;
1234 delete _step_edit_cursor
;
1235 delete _temporary_note_group
;
1239 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1241 RegionView::region_resized(what_changed
);
1243 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1244 set_duration(_region
->length(), 0);
1245 if (_enable_display
) {
1252 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1254 RegionView::reset_width_dependent_items(pixel_width
);
1255 assert(_pixel_width
== pixel_width
);
1257 if (_enable_display
) {
1261 move_step_edit_cursor (_step_edit_cursor_position
);
1262 set_step_edit_cursor_width (_step_edit_cursor_width
);
1266 MidiRegionView::set_height (double height
)
1268 static const double FUDGE
= 2.0;
1269 const double old_height
= _height
;
1270 RegionView::set_height(height
);
1271 _height
= height
- FUDGE
;
1273 apply_note_range(midi_stream_view()->lowest_note(),
1274 midi_stream_view()->highest_note(),
1275 height
!= old_height
+ FUDGE
);
1278 name_pixbuf
->raise_to_top();
1281 for (PatchChanges::iterator x
= _patch_changes
.begin(); x
!= _patch_changes
.end(); ++x
) {
1282 (*x
)->set_height (midi_stream_view()->contents_height());
1285 if (_step_edit_cursor
) {
1286 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
1291 /** Apply the current note range from the stream view
1292 * by repositioning/hiding notes as necessary
1295 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1297 if (!_enable_display
) {
1301 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1305 _current_range_min
= min
;
1306 _current_range_max
= max
;
1308 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1309 CanvasNoteEvent
* event
= *i
;
1310 boost::shared_ptr
<NoteType
> note (event
->note());
1312 if (note
->note() < _current_range_min
||
1313 note
->note() > _current_range_max
) {
1319 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1321 const double y1
= midi_stream_view()->note_to_y(note
->note());
1322 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1324 cnote
->property_y1() = y1
;
1325 cnote
->property_y2() = y2
;
1327 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1329 const double diamond_size
= update_hit (chit
);
1331 chit
->set_height (diamond_size
);
1337 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1341 double unit_position
= _region
->position () / samples_per_unit
;
1342 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1343 MidiGhostRegion
* ghost
;
1345 if (mtv
&& mtv
->midi_view()) {
1346 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1347 to allow having midi notes on top of note lines and waveforms.
1349 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1351 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1354 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1355 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1356 ghost
->add_note(note
);
1360 ghost
->set_height ();
1361 ghost
->set_duration (_region
->length() / samples_per_unit
);
1362 ghosts
.push_back (ghost
);
1364 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1370 /** Begin tracking note state for successive calls to add_event
1373 MidiRegionView::begin_write()
1375 assert(!_active_notes
);
1376 _active_notes
= new CanvasNote
*[128];
1377 for (unsigned i
=0; i
< 128; ++i
) {
1378 _active_notes
[i
] = 0;
1383 /** Destroy note state for add_event
1386 MidiRegionView::end_write()
1388 delete[] _active_notes
;
1390 _marked_for_selection
.clear();
1391 _marked_for_velocity
.clear();
1395 /** Resolve an active MIDI note (while recording).
1398 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1400 if (midi_view()->note_mode() != Sustained
) {
1404 if (_active_notes
&& _active_notes
[note
]) {
1406 const framepos_t end_time_frames
= beats_to_frames(end_time
);
1408 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1409 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1410 _active_notes
[note
] = 0;
1415 /** Extend active notes to rightmost edge of region (if length is changed)
1418 MidiRegionView::extend_active_notes()
1420 if (!_active_notes
) {
1424 for (unsigned i
=0; i
< 128; ++i
) {
1425 if (_active_notes
[i
]) {
1426 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1433 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1435 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1439 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1441 if (!route_ui
|| !route_ui
->midi_track()) {
1445 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1451 MidiRegionView::play_midi_chord (vector
<boost::shared_ptr
<NoteType
> > notes
)
1453 if (_no_sound_notes
|| !trackview
.editor().sound_notes()) {
1457 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1459 if (!route_ui
|| !route_ui
->midi_track()) {
1463 NotePlayer
* np
= new NotePlayer (route_ui
->midi_track());
1465 for (vector
<boost::shared_ptr
<NoteType
> >::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1474 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1476 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1478 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1479 (note_start_frames
< _region
->start());
1481 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1482 (note
->note() <= midi_stream_view()->highest_note());
1487 /** Update a canvas note's size from its model note.
1488 * @param ev Canvas note to update.
1489 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1492 MidiRegionView::update_note (CanvasNote
* ev
, bool update_ghost_regions
)
1494 boost::shared_ptr
<NoteType
> note
= ev
->note();
1496 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1498 /* trim note display to not overlap the end of its region */
1499 const framepos_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1501 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1502 const double y1
= midi_stream_view()->note_to_y(note
->note());
1503 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1505 ev
->property_x1() = x
;
1506 ev
->property_y1() = y1
;
1508 if (note
->length() > 0) {
1509 ev
->property_x2() = note_endpixel
;
1511 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1514 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1516 if (note
->length() == 0) {
1517 if (_active_notes
) {
1518 assert(note
->note() < 128);
1519 // If this note is already active there's a stuck note,
1520 // finish the old note rectangle
1521 if (_active_notes
[note
->note()]) {
1522 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1523 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1524 old_rect
->property_x2() = x
;
1525 old_rect
->property_outline_what() = (guint32
) 0xF;
1527 _active_notes
[note
->note()] = ev
;
1529 /* outline all but right edge */
1530 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1532 /* outline all edges */
1533 ev
->property_outline_what() = (guint32
) 0xF;
1536 if (update_ghost_regions
) {
1537 for (std::vector
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
1538 MidiGhostRegion
* gr
= dynamic_cast<MidiGhostRegion
*> (*i
);
1540 gr
->update_note (ev
);
1547 MidiRegionView::update_hit (CanvasHit
* ev
)
1549 boost::shared_ptr
<NoteType
> note
= ev
->note();
1551 const framepos_t note_start_frames
= beats_to_frames(note
->time());
1552 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1553 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1554 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1558 return diamond_size
;
1561 /** Add a MIDI note to the view (with length).
1563 * If in sustained mode, notes with length 0 will be considered active
1564 * notes, and resolve_note should be called when the corresponding note off
1565 * event arrives, to properly display the note.
1568 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1570 CanvasNoteEvent
* event
= 0;
1572 assert(note
->time() >= 0);
1573 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1575 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1577 if (midi_view()->note_mode() == Sustained
) {
1579 CanvasNote
* ev_rect
= new CanvasNote(*this, *_note_group
, note
);
1581 update_note (ev_rect
);
1585 MidiGhostRegion
* gr
;
1587 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1588 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1589 gr
->add_note(ev_rect
);
1593 } else if (midi_view()->note_mode() == Percussive
) {
1595 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1597 CanvasHit
* ev_diamond
= new CanvasHit(*this, *_note_group
, diamond_size
, note
);
1599 update_hit (ev_diamond
);
1608 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1609 note_selected(event
, true);
1612 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1613 event
->show_velocity();
1616 event
->on_channel_selection_change(_last_channel_selection
);
1617 _events
.push_back(event
);
1626 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1627 MidiStreamView
* const view
= mtv
->midi_view();
1629 view
->update_note_range(note
->note());
1633 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1634 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1636 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1638 /* potentially extend region to hold new note */
1640 framepos_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1641 framepos_t region_end
= _region
->position() + _region
->length() - 1;
1643 if (end_frame
> region_end
) {
1644 _region
->set_length (end_frame
- _region
->position());
1647 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1648 MidiStreamView
* const view
= mtv
->midi_view();
1650 view
->update_note_range(new_note
->note());
1652 _marked_for_selection
.clear ();
1655 start_note_diff_command (_("step add"));
1656 note_diff_add_note (new_note
, true, false);
1659 // last_step_edit_note = new_note;
1663 MidiRegionView::step_sustain (Evoral::MusicalTime beats
)
1665 change_note_lengths (false, false, beats
, false, true);
1669 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch
, const string
& displaytext
)
1671 assert (patch
->time() >= 0);
1673 const double x
= trackview
.editor().frame_to_pixel (beats_to_frames (patch
->time()));
1675 double const height
= midi_stream_view()->contents_height();
1677 boost::shared_ptr
<CanvasPatchChange
> patch_change
= boost::shared_ptr
<CanvasPatchChange
>(
1678 new CanvasPatchChange(*this, *_note_group
,
1683 _custom_device_mode
,
1687 // Show unless patch change is beyond the region bounds
1688 if (patch
->time() - _region
->start() >= _region
->length() || patch
->time() < _region
->start()) {
1689 patch_change
->hide();
1691 patch_change
->show();
1694 _patch_changes
.push_back (patch_change
);
1698 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1700 MidiModel::PatchChanges::iterator i
= _model
->patch_change_lower_bound (time
);
1701 while (i
!= _model
->patch_changes().end() && (*i
)->channel() != channel
) {
1705 if (i
!= _model
->patch_changes().end()) {
1706 key
.msb
= (*i
)->bank_msb ();
1707 key
.lsb
= (*i
)->bank_lsb ();
1708 key
.program_number
= (*i
)->program ();
1710 key
.msb
= key
.lsb
= key
.program_number
= 0;
1713 assert (key
.is_sane());
1718 MidiRegionView::change_patch_change (CanvasPatchChange
& pc
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1720 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1722 if (pc
.patch()->program() != new_patch
.program_number
) {
1723 c
->change_program (pc
.patch (), new_patch
.program_number
);
1726 int const new_bank
= (new_patch
.msb
<< 7) | new_patch
.lsb
;
1727 if (pc
.patch()->bank() != new_bank
) {
1728 c
->change_bank (pc
.patch (), new_bank
);
1731 _model
->apply_command (*trackview
.session(), c
);
1733 _patch_changes
.clear ();
1734 display_patch_changes ();
1738 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change
, const Evoral::PatchChange
<Evoral::MusicalTime
> & new_change
)
1740 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("alter patch change"));
1742 if (old_change
->time() != new_change
.time()) {
1743 c
->change_time (old_change
, new_change
.time());
1746 if (old_change
->channel() != new_change
.channel()) {
1747 c
->change_channel (old_change
, new_change
.channel());
1750 if (old_change
->program() != new_change
.program()) {
1751 c
->change_program (old_change
, new_change
.program());
1754 if (old_change
->bank() != new_change
.bank()) {
1755 c
->change_bank (old_change
, new_change
.bank());
1758 _model
->apply_command (*trackview
.session(), c
);
1760 _patch_changes
.clear ();
1761 display_patch_changes ();
1764 /** Add a patch change to the region.
1765 * @param t Time in frames relative to region position
1766 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1767 * MidiTimeAxisView::get_channel_for_add())
1770 MidiRegionView::add_patch_change (framecnt_t t
, Evoral::PatchChange
<Evoral::MusicalTime
> const & patch
)
1772 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
1774 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("add patch change"));
1775 c
->add (MidiModel::PatchChangePtr (
1776 new Evoral::PatchChange
<Evoral::MusicalTime
> (
1777 frames_to_beats (t
+ midi_region()->start()), mtv
->get_channel_for_add(), patch
.program(), patch
.bank()
1781 _model
->apply_command (*trackview
.session(), c
);
1783 _patch_changes
.clear ();
1784 display_patch_changes ();
1788 MidiRegionView::move_patch_change (CanvasPatchChange
& pc
, Evoral::MusicalTime t
)
1790 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("move patch change"));
1791 c
->change_time (pc
.patch (), t
);
1792 _model
->apply_command (*trackview
.session(), c
);
1794 _patch_changes
.clear ();
1795 display_patch_changes ();
1799 MidiRegionView::delete_patch_change (CanvasPatchChange
* pc
)
1801 MidiModel::PatchChangeDiffCommand
* c
= _model
->new_patch_change_diff_command (_("delete patch change"));
1802 c
->remove (pc
->patch ());
1803 _model
->apply_command (*trackview
.session(), c
);
1805 _patch_changes
.clear ();
1806 display_patch_changes ();
1810 MidiRegionView::previous_patch (CanvasPatchChange
& patch
)
1812 if (patch
.patch()->program() < 127) {
1813 MIDI::Name::PatchPrimaryKey key
;
1814 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1815 key
.program_number
++;
1816 change_patch_change (patch
, key
);
1821 MidiRegionView::next_patch (CanvasPatchChange
& patch
)
1823 if (patch
.patch()->program() > 0) {
1824 MIDI::Name::PatchPrimaryKey key
;
1825 get_patch_key_at (patch
.patch()->time(), patch
.patch()->channel(), key
);
1826 key
.program_number
--;
1827 change_patch_change (patch
, key
);
1832 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1834 if (_selection
.empty()) {
1838 _selection
.erase (cne
);
1842 MidiRegionView::delete_selection()
1844 if (_selection
.empty()) {
1848 start_note_diff_command (_("delete selection"));
1850 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1851 if ((*i
)->selected()) {
1852 _note_diff_command
->remove((*i
)->note());
1862 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1864 start_note_diff_command (_("delete note"));
1865 _note_diff_command
->remove (n
);
1868 trackview
.editor().verbose_cursor()->hide ();
1872 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1874 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1875 if ((*i
)->selected() && (*i
) != ev
) {
1876 (*i
)->set_selected(false);
1877 (*i
)->hide_velocity();
1885 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1887 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1890 Selection::iterator tmp
= i
;
1893 (*i
)->set_selected (false);
1894 _selection
.erase (i
);
1903 /* don't bother with removing this regionview from the editor selection,
1904 since we're about to add another note, and thus put/keep this
1905 regionview in the editor selection.
1908 if (!ev
->selected()) {
1909 add_to_selection (ev
);
1914 MidiRegionView::select_all_notes ()
1918 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1919 add_to_selection (*i
);
1924 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1926 uint8_t low_note
= 127;
1927 uint8_t high_note
= 0;
1928 MidiModel::Notes
& notes (_model
->notes());
1929 _optimization_iterator
= _events
.begin();
1935 if (extend
&& _selection
.empty()) {
1941 /* scan existing selection to get note range */
1943 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1944 if ((*i
)->note()->note() < low_note
) {
1945 low_note
= (*i
)->note()->note();
1947 if ((*i
)->note()->note() > high_note
) {
1948 high_note
= (*i
)->note()->note();
1952 low_note
= min (low_note
, notenum
);
1953 high_note
= max (high_note
, notenum
);
1956 _no_sound_notes
= true;
1958 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1960 boost::shared_ptr
<NoteType
> note (*n
);
1961 CanvasNoteEvent
* cne
;
1962 bool select
= false;
1964 if (((1 << note
->channel()) & channel_mask
) != 0) {
1966 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1969 } else if (note
->note() == notenum
) {
1975 if ((cne
= find_canvas_note (note
)) != 0) {
1976 // extend is false because we've taken care of it,
1977 // since it extends by time range, not pitch.
1978 note_selected (cne
, add
, false);
1982 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1986 _no_sound_notes
= false;
1990 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
1992 MidiModel::Notes
& notes (_model
->notes());
1993 _optimization_iterator
= _events
.begin();
1995 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1997 boost::shared_ptr
<NoteType
> note (*n
);
1998 CanvasNoteEvent
* cne
;
2000 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
2001 if ((cne
= find_canvas_note (note
)) != 0) {
2002 if (cne
->selected()) {
2003 note_deselected (cne
);
2005 note_selected (cne
, true, false);
2013 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
2016 clear_selection_except(ev
);
2021 if (!ev
->selected()) {
2022 add_to_selection (ev
);
2026 /* find end of latest note selected, select all between that and the start of "ev" */
2028 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2029 Evoral::MusicalTime latest
= 0;
2031 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2032 if ((*i
)->note()->end_time() > latest
) {
2033 latest
= (*i
)->note()->end_time();
2035 if ((*i
)->note()->time() < earliest
) {
2036 earliest
= (*i
)->note()->time();
2040 if (ev
->note()->end_time() > latest
) {
2041 latest
= ev
->note()->end_time();
2044 if (ev
->note()->time() < earliest
) {
2045 earliest
= ev
->note()->time();
2048 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2050 /* find notes entirely within OR spanning the earliest..latest range */
2052 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
2053 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
2054 add_to_selection (*i
);
2058 /* if events were guaranteed to be time sorted, we could do this.
2059 but as of sept 10th 2009, they no longer are.
2062 if ((*i
)->note()->time() > latest
) {
2071 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
2073 remove_from_selection (ev
);
2077 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
2087 // TODO: Make this faster by storing the last updated selection rect, and only
2088 // adjusting things that are in the area that appears/disappeared.
2089 // We probably need a tree to be able to find events in O(log(n)) time.
2091 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2093 /* check if any corner of the note is inside the rect
2096 1) this is computing "touched by", not "contained by" the rect.
2097 2) this does not require that events be sorted in time.
2100 const double ix1
= (*i
)->x1();
2101 const double ix2
= (*i
)->x2();
2102 const double iy1
= (*i
)->y1();
2103 const double iy2
= (*i
)->y2();
2105 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2106 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
2107 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
2108 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
2111 if (!(*i
)->selected()) {
2112 add_to_selection (*i
);
2114 } else if ((*i
)->selected()) {
2115 // Not inside rectangle
2116 remove_from_selection (*i
);
2122 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
2124 Selection::iterator i
= _selection
.find (ev
);
2126 if (i
!= _selection
.end()) {
2127 _selection
.erase (i
);
2130 ev
->set_selected (false);
2131 ev
->hide_velocity ();
2133 if (_selection
.empty()) {
2134 PublicEditor
& editor (trackview
.editor());
2135 editor
.get_selection().remove (this);
2140 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
2142 bool add_mrv_selection
= false;
2144 if (_selection
.empty()) {
2145 add_mrv_selection
= true;
2148 if (_selection
.insert (ev
).second
) {
2149 ev
->set_selected (true);
2150 play_midi_note ((ev
)->note());
2153 if (add_mrv_selection
) {
2154 PublicEditor
& editor (trackview
.editor());
2155 editor
.get_selection().add (this);
2160 MidiRegionView::move_selection(double dx
, double dy
, double cumulative_dy
)
2162 typedef vector
<boost::shared_ptr
<NoteType
> > PossibleChord
;
2163 PossibleChord to_play
;
2164 Evoral::MusicalTime earliest
= Evoral::MaxMusicalTime
;
2166 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2167 if ((*i
)->note()->time() < earliest
) {
2168 earliest
= (*i
)->note()->time();
2172 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2173 if (Evoral::musical_time_equal ((*i
)->note()->time(), earliest
)) {
2174 to_play
.push_back ((*i
)->note());
2176 (*i
)->move_event(dx
, dy
);
2179 if (dy
&& !_selection
.empty() && !_no_sound_notes
&& trackview
.editor().sound_notes()) {
2181 if (to_play
.size() > 1) {
2183 PossibleChord shifted
;
2185 for (PossibleChord::iterator n
= to_play
.begin(); n
!= to_play
.end(); ++n
) {
2186 boost::shared_ptr
<NoteType
> moved_note (new NoteType (**n
));
2187 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2188 shifted
.push_back (moved_note
);
2191 play_midi_chord (shifted
);
2193 } else if (!to_play
.empty()) {
2195 boost::shared_ptr
<NoteType
> moved_note (new NoteType (*to_play
.front()));
2196 moved_note
->set_note (moved_note
->note() + cumulative_dy
);
2197 play_midi_note (moved_note
);
2203 MidiRegionView::note_dropped(CanvasNoteEvent
*, frameoffset_t dt
, int8_t dnote
)
2205 assert (!_selection
.empty());
2207 uint8_t lowest_note_in_selection
= 127;
2208 uint8_t highest_note_in_selection
= 0;
2209 uint8_t highest_note_difference
= 0;
2211 // find highest and lowest notes first
2213 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2214 uint8_t pitch
= (*i
)->note()->note();
2215 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
2216 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
2220 cerr << "dnote: " << (int) dnote << endl;
2221 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2222 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2223 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2224 << int(highest_note_in_selection) << endl;
2225 cerr << "selection size: " << _selection.size() << endl;
2226 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2229 // Make sure the note pitch does not exceed the MIDI standard range
2230 if (highest_note_in_selection
+ dnote
> 127) {
2231 highest_note_difference
= highest_note_in_selection
- 127;
2234 start_note_diff_command (_("move notes"));
2236 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
2238 Evoral::MusicalTime new_time
= frames_to_beats (beats_to_frames ((*i
)->note()->time()) + dt
);
2244 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2246 uint8_t original_pitch
= (*i
)->note()->note();
2247 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
2249 // keep notes in standard midi range
2250 clamp_to_0_127(new_pitch
);
2252 // keep original pitch if note is dragged outside valid midi range
2253 if ((original_pitch
!= 0 && new_pitch
== 0)
2254 || (original_pitch
!= 127 && new_pitch
== 127)) {
2255 new_pitch
= original_pitch
;
2258 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2259 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2261 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::NoteNumber
, new_pitch
);
2266 // care about notes being moved beyond the upper/lower bounds on the canvas
2267 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2268 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2269 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2274 MidiRegionView::snap_pixel_to_frame(double x
)
2276 PublicEditor
& editor (trackview
.editor());
2277 return snap_frame_to_frame (editor
.pixel_to_frame (x
));
2280 /** Snap a frame offset within our region using the current snap settings.
2281 * @param x Frame offset from this region's position.
2282 * @return Snapped frame offset from this region's position.
2285 MidiRegionView::snap_frame_to_frame (frameoffset_t x
)
2287 PublicEditor
& editor
= trackview
.editor();
2289 /* x is region relative, convert it to global absolute frames */
2290 framepos_t
const session_frame
= x
+ _region
->position();
2292 /* try a snap in either direction */
2293 framepos_t frame
= session_frame
;
2294 editor
.snap_to (frame
, 0);
2296 /* if we went off the beginning of the region, snap forwards */
2297 if (frame
< _region
->position ()) {
2298 frame
= session_frame
;
2299 editor
.snap_to (frame
, 1);
2302 /* back to region relative */
2303 return frame
- _region
->position();
2307 MidiRegionView::snap_to_pixel(double x
)
2309 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2313 MidiRegionView::get_position_pixels()
2315 framepos_t region_frame
= get_position();
2316 return trackview
.editor().frame_to_pixel(region_frame
);
2320 MidiRegionView::get_end_position_pixels()
2322 framepos_t frame
= get_position() + get_duration ();
2323 return trackview
.editor().frame_to_pixel(frame
);
2327 MidiRegionView::beats_to_frames(double beats
) const
2329 return _time_converter
.to(beats
);
2333 MidiRegionView::frames_to_beats(framepos_t frames
) const
2335 return _time_converter
.from(frames
);
2339 MidiRegionView::begin_resizing (bool /*at_front*/)
2341 _resize_data
.clear();
2343 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2344 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2346 // only insert CanvasNotes into the map
2348 NoteResizeData
*resize_data
= new NoteResizeData();
2349 resize_data
->canvas_note
= note
;
2351 // create a new SimpleRect from the note which will be the resize preview
2352 SimpleRect
*resize_rect
= new SimpleRect(
2353 *_note_group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2355 // calculate the colors: get the color settings
2356 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2357 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2360 // make the resize preview notes more transparent and bright
2361 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2363 // calculate color based on note velocity
2364 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2365 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2369 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2370 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2372 resize_data
->resize_rect
= resize_rect
;
2373 _resize_data
.push_back(resize_data
);
2378 /** Update resizing notes while user drags.
2379 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2380 * @param at_front which end of the note (true == note on, false == note off)
2381 * @param delta_x change in mouse position since the start of the drag
2382 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2383 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2384 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2385 * as the \a primary note.
2388 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2390 bool cursor_set
= false;
2392 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2393 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2394 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2399 current_x
= canvas_note
->x1() + delta_x
;
2401 current_x
= primary
->x1() + delta_x
;
2405 current_x
= canvas_note
->x2() + delta_x
;
2407 current_x
= primary
->x2() + delta_x
;
2412 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2413 resize_rect
->property_x2() = canvas_note
->x2();
2415 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2416 resize_rect
->property_x1() = canvas_note
->x1();
2422 beats
= snap_pixel_to_frame (current_x
);
2423 beats
= frames_to_beats (beats
);
2428 if (beats
< canvas_note
->note()->end_time()) {
2429 len
= canvas_note
->note()->time() - beats
;
2430 len
+= canvas_note
->note()->length();
2435 if (beats
>= canvas_note
->note()->time()) {
2436 len
= beats
- canvas_note
->note()->time();
2443 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2444 show_verbose_cursor (buf
, 0, 0);
2453 /** Finish resizing notes when the user releases the mouse button.
2454 * Parameters the same as for \a update_resizing().
2457 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent
* primary
, bool at_front
, double delta_x
, bool relative
)
2459 start_note_diff_command (_("resize notes"));
2461 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2462 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2463 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2465 /* Get the new x position for this resize, which is in pixels relative
2466 * to the region position.
2473 current_x
= canvas_note
->x1() + delta_x
;
2475 current_x
= primary
->x1() + delta_x
;
2479 current_x
= canvas_note
->x2() + delta_x
;
2481 current_x
= primary
->x2() + delta_x
;
2485 /* Convert that to a frame within the region */
2486 current_x
= snap_pixel_to_frame (current_x
) + _region
->start ();
2488 /* and then to beats */
2489 current_x
= frames_to_beats (current_x
);
2491 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2492 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::StartTime
, current_x
);
2494 double len
= canvas_note
->note()->time() - current_x
;
2495 len
+= canvas_note
->note()->length();
2498 /* XXX convert to beats */
2499 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2504 double len
= current_x
- canvas_note
->note()->time();
2507 /* XXX convert to beats */
2508 note_diff_add_change (canvas_note
, MidiModel::NoteDiffCommand::Length
, len
);
2516 _resize_data
.clear();
2521 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2523 uint8_t new_velocity
;
2526 new_velocity
= event
->note()->velocity() + velocity
;
2527 clamp_to_0_127(new_velocity
);
2529 new_velocity
= velocity
;
2532 event
->set_selected (event
->selected()); // change color
2534 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Velocity
, new_velocity
);
2538 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2543 new_note
= event
->note()->note() + note
;
2548 clamp_to_0_127 (new_note
);
2549 note_diff_add_change (event
, MidiModel::NoteDiffCommand::NoteNumber
, new_note
);
2553 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2555 bool change_start
= false;
2556 bool change_length
= false;
2557 Evoral::MusicalTime new_start
= 0;
2558 Evoral::MusicalTime new_length
= 0;
2560 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2562 front_delta: if positive - move the start of the note later in time (shortening it)
2563 if negative - move the start of the note earlier in time (lengthening it)
2565 end_delta: if positive - move the end of the note later in time (lengthening it)
2566 if negative - move the end of the note earlier in time (shortening it)
2570 if (front_delta
< 0) {
2572 if (event
->note()->time() < -front_delta
) {
2575 new_start
= event
->note()->time() + front_delta
; // moves earlier
2578 /* start moved toward zero, so move the end point out to where it used to be.
2579 Note that front_delta is negative, so this increases the length.
2582 new_length
= event
->note()->length() - front_delta
;
2583 change_start
= true;
2584 change_length
= true;
2588 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2590 if (new_pos
< event
->note()->end_time()) {
2591 new_start
= event
->note()->time() + front_delta
;
2592 /* start moved toward the end, so move the end point back to where it used to be */
2593 new_length
= event
->note()->length() - front_delta
;
2594 change_start
= true;
2595 change_length
= true;
2602 bool can_change
= true;
2603 if (end_delta
< 0) {
2604 if (event
->note()->length() < -end_delta
) {
2610 new_length
= event
->note()->length() + end_delta
;
2611 change_length
= true;
2616 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_start
);
2619 if (change_length
) {
2620 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, new_length
);
2625 MidiRegionView::change_note_channel (CanvasNoteEvent
* event
, int8_t chn
, bool relative
)
2627 uint8_t new_channel
;
2631 if (event
->note()->channel() < -chn
) {
2634 new_channel
= event
->note()->channel() + chn
;
2637 new_channel
= event
->note()->channel() + chn
;
2640 new_channel
= (uint8_t) chn
;
2643 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Channel
, new_channel
);
2647 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2649 Evoral::MusicalTime new_time
;
2653 if (event
->note()->time() < -delta
) {
2656 new_time
= event
->note()->time() + delta
;
2659 new_time
= event
->note()->time() + delta
;
2665 note_diff_add_change (event
, MidiModel::NoteDiffCommand::StartTime
, new_time
);
2669 MidiRegionView::change_note_length (CanvasNoteEvent
* event
, Evoral::MusicalTime t
)
2671 note_diff_add_change (event
, MidiModel::NoteDiffCommand::Length
, t
);
2675 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2679 if (_selection
.empty()) {
2694 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2695 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2701 start_note_diff_command (_("change velocities"));
2703 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2704 Selection::iterator next
= i
;
2706 change_note_velocity (*i
, delta
, true);
2712 if (!_selection
.empty()) {
2714 snprintf (buf
, sizeof (buf
), "Vel %d",
2715 (int) (*_selection
.begin())->note()->velocity());
2716 show_verbose_cursor (buf
, 10, 10);
2722 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2724 if (_selection
.empty()) {
2741 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2743 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2747 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2754 start_note_diff_command (_("transpose"));
2756 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2757 Selection::iterator next
= i
;
2759 change_note_note (*i
, delta
, true);
2767 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, Evoral::MusicalTime delta
, bool start
, bool end
)
2773 /* grab the current grid distance */
2775 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2777 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2778 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2788 start_note_diff_command (_("change note lengths"));
2790 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2791 Selection::iterator next
= i
;
2794 /* note the negation of the delta for start */
2796 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2805 MidiRegionView::nudge_notes (bool forward
)
2807 if (_selection
.empty()) {
2811 /* pick a note as the point along the timeline to get the nudge distance.
2812 its not necessarily the earliest note, so we may want to pull the notes out
2813 into a vector and sort before using the first one.
2816 framepos_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2818 framepos_t distance
;
2820 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2822 /* grid is off - use nudge distance */
2824 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2830 framepos_t next_pos
= ref_point
;
2833 if (max_framepos
- 1 < next_pos
) {
2837 if (next_pos
== 0) {
2843 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2844 distance
= ref_point
- next_pos
;
2847 if (distance
== 0) {
2851 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2857 start_note_diff_command (_("nudge"));
2859 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2860 Selection::iterator next
= i
;
2862 change_note_time (*i
, delta
, true);
2870 MidiRegionView::change_channel(uint8_t channel
)
2872 start_note_diff_command(_("change channel"));
2873 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2874 note_diff_add_change (*i
, MidiModel::NoteDiffCommand::Channel
, channel
);
2882 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2884 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2886 _pre_enter_cursor
= editor
->get_canvas_cursor ();
2888 if (_mouse_state
== SelectTouchDragging
) {
2889 note_selected (ev
, true);
2892 show_verbose_cursor (ev
->note ());
2896 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2898 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2900 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2901 (*i
)->hide_velocity ();
2904 editor
->verbose_cursor()->hide ();
2906 if (_pre_enter_cursor
) {
2907 editor
->set_canvas_cursor (_pre_enter_cursor
);
2908 _pre_enter_cursor
= 0;
2913 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange
* ev
)
2916 s
<< ((int) ev
->patch()->program() + 1) << ":" << (ev
->patch()->bank() + 1);
2917 show_verbose_cursor (s
.str(), 10, 20);
2921 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange
*)
2923 trackview
.editor().verbose_cursor()->hide ();
2927 MidiRegionView::note_mouse_position (float x_fraction
, float /*y_fraction*/, bool can_set_cursor
)
2929 Editor
* editor
= dynamic_cast<Editor
*>(&trackview
.editor());
2931 if (x_fraction
> 0.0 && x_fraction
< 0.25) {
2932 editor
->set_canvas_cursor (editor
->cursors()->left_side_trim
);
2933 } else if (x_fraction
>= 0.75 && x_fraction
< 1.0) {
2934 editor
->set_canvas_cursor (editor
->cursors()->right_side_trim
);
2936 if (_pre_enter_cursor
&& can_set_cursor
) {
2937 editor
->set_canvas_cursor (_pre_enter_cursor
);
2943 MidiRegionView::set_frame_color()
2947 TimeAxisViewItem::set_frame_color ();
2954 f
= ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2955 } else if (high_enough_for_name
) {
2956 f
= ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2961 if (!rect_visible
) {
2962 f
= UINT_RGBA_CHANGE_A (f
, 0);
2965 frame
->property_fill_color_rgba() = f
;
2969 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2973 case FilterChannels
:
2974 _force_channel
= -1;
2977 _force_channel
= mask
;
2978 mask
= 0xFFFF; // Show all notes as active (below)
2981 // Update notes for selection
2982 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2983 (*i
)->on_channel_selection_change(mask
);
2986 _last_channel_selection
= mask
;
2988 _patch_changes
.clear ();
2989 display_patch_changes ();
2993 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2995 _model_name
= model
;
2996 _custom_device_mode
= custom_device_mode
;
3001 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
3003 if (_selection
.empty()) {
3007 PublicEditor
& editor (trackview
.editor());
3011 /* XXX what to do ? */
3015 editor
.get_cut_buffer().add (selection_as_cut_buffer());
3023 start_note_diff_command();
3025 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3032 note_diff_remove_note (*i
);
3042 MidiRegionView::selection_as_cut_buffer () const
3046 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
3047 NoteType
* n
= (*i
)->note().get();
3048 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
3051 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
3057 /** This method handles undo */
3059 MidiRegionView::paste (framepos_t pos
, float times
, const MidiCutBuffer
& mcb
)
3065 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("MIDI paste @ %1 times %2\n", pos
, times
));
3067 trackview
.session()->begin_reversible_command (_("paste"));
3069 start_note_diff_command (_("paste"));
3071 Evoral::MusicalTime beat_delta
;
3072 Evoral::MusicalTime paste_pos_beats
;
3073 Evoral::MusicalTime duration
;
3074 Evoral::MusicalTime end_point
= 0;
3076 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
3077 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
3078 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
3079 paste_pos_beats
= 0;
3081 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",
3082 (*mcb
.notes().begin())->time(),
3083 (*mcb
.notes().rbegin())->end_time(),
3084 duration
, pos
, _region
->position(),
3085 paste_pos_beats
, beat_delta
));
3089 for (int n
= 0; n
< (int) times
; ++n
) {
3091 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
3093 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
3094 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
3096 /* make all newly added notes selected */
3098 note_diff_add_note (copied_note
, true);
3099 end_point
= copied_note
->end_time();
3102 paste_pos_beats
+= duration
;
3105 /* if we pasted past the current end of the region, extend the region */
3107 framepos_t end_frame
= _region
->position() + beats_to_frames (end_point
);
3108 framepos_t region_end
= _region
->position() + _region
->length() - 1;
3110 if (end_frame
> region_end
) {
3112 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("Paste extended region from %1 to %2\n", region_end
, end_frame
));
3114 _region
->clear_changes ();
3115 _region
->set_length (end_frame
);
3116 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
3121 trackview
.session()->commit_reversible_command ();
3124 struct EventNoteTimeEarlyFirstComparator
{
3125 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
3126 return a
->note()->time() < b
->note()->time();
3131 MidiRegionView::time_sort_events ()
3133 if (!_sort_needed
) {
3137 EventNoteTimeEarlyFirstComparator cmp
;
3140 _sort_needed
= false;
3144 MidiRegionView::goto_next_note ()
3146 // framepos_t pos = -1;
3147 bool use_next
= false;
3149 if (_events
.back()->selected()) {
3153 time_sort_events ();
3155 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3156 if ((*i
)->selected()) {
3159 } else if (use_next
) {
3161 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3166 /* use the first one */
3168 unique_select (_events
.front());
3173 MidiRegionView::goto_previous_note ()
3175 // framepos_t pos = -1;
3176 bool use_next
= false;
3178 if (_events
.front()->selected()) {
3182 time_sort_events ();
3184 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
3185 if ((*i
)->selected()) {
3188 } else if (use_next
) {
3190 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3195 /* use the last one */
3197 unique_select (*(_events
.rbegin()));
3201 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
3203 bool had_selected
= false;
3205 time_sort_events ();
3207 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3208 if ((*i
)->selected()) {
3209 selected
.insert ((*i
)->note());
3210 had_selected
= true;
3214 if (allow_all_if_none_selected
&& !had_selected
) {
3215 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3216 selected
.insert ((*i
)->note());
3222 MidiRegionView::update_ghost_note (double x
, double y
)
3224 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3229 _note_group
->w2i (x
, y
);
3230 framepos_t
const f
= snap_pixel_to_frame (x
);
3233 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
3239 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
3241 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
3242 _ghost_note
->note()->set_length (length
);
3243 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
3244 _ghost_note
->note()->set_channel (mtv
->get_channel_for_add ());
3246 /* the ghost note does not appear in ghost regions, so pass false in here */
3247 update_note (_ghost_note
, false);
3249 show_verbose_cursor (_ghost_note
->note ());
3253 MidiRegionView::create_ghost_note (double x
, double y
)
3258 boost::shared_ptr
<NoteType
> g (new NoteType
);
3259 _ghost_note
= new NoEventCanvasNote (*this, *_note_group
, g
);
3260 _ghost_note
->property_outline_color_rgba() = 0x000000aa;
3261 update_ghost_note (x
, y
);
3262 _ghost_note
->show ();
3267 show_verbose_cursor (_ghost_note
->note ());
3271 MidiRegionView::snap_changed ()
3277 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
3281 MidiRegionView::drop_down_keys ()
3283 _mouse_state
= None
;
3287 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double /*x*/, double y
)
3289 double note
= midi_stream_view()->y_to_note(y
);
3291 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
3293 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
3295 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
3296 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3297 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
3298 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
3303 bool add_mrv_selection
= false;
3305 if (_selection
.empty()) {
3306 add_mrv_selection
= true;
3309 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
3310 if (_selection
.insert (*i
).second
) {
3311 (*i
)->set_selected (true);
3315 if (add_mrv_selection
) {
3316 PublicEditor
& editor (trackview
.editor());
3317 editor
.get_selection().add (this);
3322 MidiRegionView::color_handler ()
3324 RegionView::color_handler ();
3326 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
3327 (*i
)->set_selected ((*i
)->selected()); // will change color
3330 /* XXX probably more to do here */
3334 MidiRegionView::enable_display (bool yn
)
3336 RegionView::enable_display (yn
);
3343 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos
)
3345 if (_step_edit_cursor
== 0) {
3346 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
3348 _step_edit_cursor
= new ArdourCanvas::SimpleRect (*group
);
3349 _step_edit_cursor
->property_y1() = 0;
3350 _step_edit_cursor
->property_y2() = midi_stream_view()->contents_height();
3351 _step_edit_cursor
->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3352 _step_edit_cursor
->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3355 move_step_edit_cursor (pos
);
3356 _step_edit_cursor
->show ();
3360 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos
)
3362 _step_edit_cursor_position
= pos
;
3364 if (_step_edit_cursor
) {
3365 double pixel
= trackview
.editor().frame_to_pixel (beats_to_frames (pos
));
3366 _step_edit_cursor
->property_x1() = pixel
;
3367 set_step_edit_cursor_width (_step_edit_cursor_width
);
3372 MidiRegionView::hide_step_edit_cursor ()
3374 if (_step_edit_cursor
) {
3375 _step_edit_cursor
->hide ();
3380 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats
)
3382 _step_edit_cursor_width
= beats
;
3384 if (_step_edit_cursor
) {
3385 _step_edit_cursor
->property_x2() = _step_edit_cursor
->property_x1() + trackview
.editor().frame_to_pixel (beats_to_frames (beats
));
3389 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3390 * @param buf Data that has been recorded.
3391 * @param w Source that this data will end up in.
3394 MidiRegionView::data_recorded (boost::shared_ptr
<MidiBuffer
> buf
, boost::weak_ptr
<MidiSource
> w
)
3396 if (!_active_notes
) {
3397 /* we aren't actively being recorded to */
3401 boost::shared_ptr
<MidiSource
> src
= w
.lock ();
3402 if (!src
|| src
!= midi_region()->midi_source()) {
3403 /* recorded data was not destined for our source */
3407 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*> (&trackview
);
3408 BeatsFramesConverter
converter (trackview
.session()->tempo_map(), mtv
->midi_track()->get_capture_start_frame (0));
3410 framepos_t back
= max_framepos
;
3412 for (MidiBuffer::iterator i
= buf
->begin(); i
!= buf
->end(); ++i
) {
3413 Evoral::MIDIEvent
<MidiBuffer::TimeType
> const ev (*i
, false);
3414 assert (ev
.buffer ());
3416 Evoral::MusicalTime
const time_beats
= converter
.from (ev
.time () - converter
.origin_b ());
3418 if (ev
.type() == MIDI_CMD_NOTE_ON
) {
3420 boost::shared_ptr
<NoteType
> note (
3421 new NoteType (ev
.channel(), time_beats
, 0, ev
.note(), ev
.velocity())
3424 add_note (note
, true);
3426 /* fix up our note range */
3427 if (ev
.note() < _current_range_min
) {
3428 midi_stream_view()->apply_note_range (ev
.note(), _current_range_max
, true);
3429 } else if (ev
.note() > _current_range_max
) {
3430 midi_stream_view()->apply_note_range (_current_range_min
, ev
.note(), true);
3433 } else if (ev
.type() == MIDI_CMD_NOTE_OFF
) {
3434 resolve_note (ev
.note (), time_beats
);
3440 midi_stream_view()->check_record_layers (region(), back
);
3444 MidiRegionView::trim_front_starting ()
3446 /* Reparent the note group to the region view's parent, so that it doesn't change
3447 when the region view is trimmed.
3449 _temporary_note_group
= new ArdourCanvas::Group (*group
->property_parent ());
3450 _temporary_note_group
->move (group
->property_x(), group
->property_y());
3451 _note_group
->reparent (*_temporary_note_group
);
3455 MidiRegionView::trim_front_ending ()
3457 _note_group
->reparent (*group
);
3458 delete _temporary_note_group
;
3459 _temporary_note_group
= 0;
3461 if (_region
->start() < 0) {
3462 /* Trim drag made start time -ve; fix this */
3463 midi_region()->fix_negative_start ();
3468 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange
* pc
)
3470 PatchChangeDialog
d (&_time_converter
, trackview
.session(), *pc
->patch (), Gtk::Stock::APPLY
);
3471 if (d
.run () != Gtk::RESPONSE_ACCEPT
) {
3475 change_patch_change (pc
->patch(), d
.patch ());
3480 MidiRegionView::show_verbose_cursor (boost::shared_ptr
<NoteType
> n
) const
3483 snprintf (buf
, sizeof (buf
), "%s (%d) Chn %d\nVel %d",
3484 Evoral::midi_note_name (n
->note()).c_str(),
3486 (int) n
->channel() + 1,
3487 (int) n
->velocity());
3489 show_verbose_cursor (buf
, 10, 20);
3493 MidiRegionView::show_verbose_cursor (string
const & text
, double xoffset
, double yoffset
) const
3497 trackview
.editor().get_pointer_position (wx
, wy
);
3502 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3504 double x1
, y1
, x2
, y2
;
3505 trackview
.editor().verbose_cursor()->canvas_item()->get_bounds (x1
, y1
, x2
, y2
);
3507 if ((wy
+ y2
- y1
) > trackview
.editor().canvas_height()) {
3508 wy
-= (y2
- y1
) + 2 * yoffset
;
3511 trackview
.editor().verbose_cursor()->set (text
, wx
, wy
);
3512 trackview
.editor().verbose_cursor()->show ();