2 Copyright (C) 2001-2007 Paul Davis
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-program-change.h"
52 #include "ghostregion.h"
53 #include "gui_thread.h"
55 #include "midi_cut_buffer.h"
56 #include "midi_list_editor.h"
57 #include "midi_region_view.h"
58 #include "midi_streamview.h"
59 #include "midi_time_axis.h"
60 #include "midi_time_axis.h"
61 #include "midi_util.h"
62 #include "public_editor.h"
63 #include "selection.h"
64 #include "simpleline.h"
65 #include "streamview.h"
70 using namespace ARDOUR
;
72 using namespace Editing
;
73 using namespace ArdourCanvas
;
74 using Gtkmm2ext::Keyboard
;
76 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
77 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
const & basic_color
)
78 : RegionView (parent
, tv
, r
, spu
, basic_color
)
80 , _last_channel_selection(0xFFFF)
81 , _current_range_min(0)
82 , _current_range_max(0)
83 , _model_name(string())
84 , _custom_device_mode(string())
86 , _note_group(new ArdourCanvas::Group(*parent
))
93 , _optimization_iterator (_events
.end())
95 , no_sound_notes (false)
97 _note_group
->raise_to_top();
98 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
101 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
102 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
& basic_color
,
103 TimeAxisViewItem::Visibility visibility
)
104 : RegionView (parent
, tv
, r
, spu
, basic_color
, false, visibility
)
106 , _last_channel_selection(0xFFFF)
107 , _model_name(string())
108 , _custom_device_mode(string())
110 , _note_group(new ArdourCanvas::Group(*parent
))
116 , _sort_needed (true)
117 , _optimization_iterator (_events
.end())
119 , no_sound_notes (false)
121 _note_group
->raise_to_top();
122 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
125 MidiRegionView::MidiRegionView (const MidiRegionView
& other
)
126 : sigc::trackable(other
)
129 , _last_channel_selection(0xFFFF)
130 , _model_name(string())
131 , _custom_device_mode(string())
133 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
139 , _sort_needed (true)
140 , _optimization_iterator (_events
.end())
142 , no_sound_notes (false)
147 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
148 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
153 MidiRegionView::MidiRegionView (const MidiRegionView
& other
, boost::shared_ptr
<MidiRegion
> region
)
154 : RegionView (other
, boost::shared_ptr
<Region
> (region
))
156 , _last_channel_selection(0xFFFF)
157 , _model_name(string())
158 , _custom_device_mode(string())
160 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
166 , _sort_needed (true)
167 , _optimization_iterator (_events
.end())
169 , no_sound_notes (false)
174 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
175 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
181 MidiRegionView::init (Gdk::Color
const & basic_color
, bool wfd
)
183 PublicEditor::DropDownKeys
.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys
));
185 CanvasNoteEvent::CanvasNoteEventDeleted
.connect (note_delete_connection
, MISSING_INVALIDATOR
,
186 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection
, this, _1
),
190 midi_region()->midi_source(0)->load_model();
193 _model
= midi_region()->midi_source(0)->model();
194 _enable_display
= false;
196 RegionView::init (basic_color
, false);
198 compute_colors (basic_color
);
200 set_height (trackview
.current_height());
203 region_sync_changed ();
204 region_resized (ARDOUR::bounds_change
);
207 reset_width_dependent_items (_pixel_width
);
211 _enable_display
= true;
214 display_model (_model
);
218 group
->raise_to_top();
219 group
->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event
), false);
221 midi_view()->signal_channel_mode_changed().connect(
222 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed
));
224 midi_view()->signal_midi_patch_settings_changed().connect(
225 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed
));
227 trackview
.editor().SnapChanged
.connect (snap_changed_connection
, invalidator (*this), ui_bind (&MidiRegionView::snap_changed
, this), gui_context ());
231 MidiRegionView::canvas_event(GdkEvent
* ev
)
233 if (!trackview
.editor().internal_editing()) {
237 /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
238 to its items, which means that ev->type == GDK_SCROLL will never be seen
243 return scroll (&ev
->scroll
);
246 return key_press (&ev
->key
);
248 case GDK_KEY_RELEASE
:
249 return key_release (&ev
->key
);
251 case GDK_BUTTON_PRESS
:
252 return button_press (&ev
->button
);
254 case GDK_2BUTTON_PRESS
:
257 case GDK_BUTTON_RELEASE
:
258 return button_release (&ev
->button
);
260 case GDK_ENTER_NOTIFY
:
261 return enter_notify (&ev
->crossing
);
263 case GDK_LEAVE_NOTIFY
:
264 return leave_notify (&ev
->crossing
);
266 case GDK_MOTION_NOTIFY
:
267 return motion (&ev
->motion
);
277 MidiRegionView::enter_notify (GdkEventCrossing
* ev
)
279 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
281 Keyboard::magic_widget_grab_focus();
284 if (trackview
.editor().current_mouse_mode() == MouseRange
) {
285 create_ghost_note (ev
->x
, ev
->y
);
292 MidiRegionView::leave_notify (GdkEventCrossing
* ev
)
294 trackview
.editor().hide_verbose_canvas_cursor ();
301 MidiRegionView::button_press (GdkEventButton
* ev
)
305 group
->w2i (_last_x
, _last_y
);
307 if (_mouse_state
!= SelectTouchDragging
&& ev
->button
== 1) {
308 _pressed_button
= ev
->button
;
309 _mouse_state
= Pressed
;
313 _pressed_button
= ev
->button
;
319 MidiRegionView::button_release (GdkEventButton
* ev
)
321 double event_x
, event_y
;
322 nframes64_t event_frame
= 0;
326 group
->w2i(event_x
, event_y
);
327 group
->ungrab(ev
->time
);
328 event_frame
= trackview
.editor().pixel_to_frame(event_x
);
330 if (ev
->button
== 3) {
332 } else if (_pressed_button
!= 1) {
336 switch (_mouse_state
) {
337 case Pressed
: // Clicked
338 switch (trackview
.editor().current_mouse_mode()) {
342 maybe_select_by_position (ev
, event_x
, event_y
);
348 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, trackview
.editor().pixel_to_frame (event_x
));
352 create_note_at (event_x
, event_y
, beats
, true);
360 case SelectRectDragging
: // Select drag done
366 case AddDragging
: // Add drag done
368 if (_drag_rect
->property_x2() > _drag_rect
->property_x1() + 2) {
369 const double x
= _drag_rect
->property_x1();
370 const double length
= trackview
.editor().pixel_to_frame
371 (_drag_rect
->property_x2() - _drag_rect
->property_x1());
373 create_note_at (x
, _drag_rect
->property_y1(), frames_to_beats(length
), false);
379 create_ghost_note (ev
->x
, ev
->y
);
389 MidiRegionView::motion (GdkEventMotion
* ev
)
391 double event_x
, event_y
;
392 nframes64_t event_frame
= 0;
396 group
->w2i(event_x
, event_y
);
398 // convert event_x to global frame
399 event_frame
= trackview
.editor().pixel_to_frame(event_x
) + _region
->position();
400 trackview
.editor().snap_to(event_frame
);
401 // convert event_frame back to local coordinates relative to position
402 event_frame
-= _region
->position();
405 update_ghost_note (ev
->x
, ev
->y
);
408 /* any motion immediately hides velocity text that may have been visible */
410 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
411 (*i
)->hide_velocity ();
414 switch (_mouse_state
) {
415 case Pressed
: // Maybe start a drag, if we've moved a bit
417 if (fabs (event_x
- _last_x
) < 1 && fabs (event_y
- _last_y
) < 1) {
418 /* no appreciable movement since the button was pressed */
423 if (_pressed_button
== 1 && trackview
.editor().current_mouse_mode() == MouseObject
) {
424 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
425 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
428 _drag_start_x
= event_x
;
429 _drag_start_y
= event_y
;
431 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
432 _drag_rect
->property_x1() = event_x
;
433 _drag_rect
->property_y1() = event_y
;
434 _drag_rect
->property_x2() = event_x
;
435 _drag_rect
->property_y2() = event_y
;
436 _drag_rect
->property_outline_what() = 0xFF;
437 _drag_rect
->property_outline_color_rgba()
438 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
439 _drag_rect
->property_fill_color_rgba()
440 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
442 _mouse_state
= SelectRectDragging
;
445 // Add note drag start
446 } else if (trackview
.editor().internal_editing()) {
451 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
452 Gdk::Cursor(Gdk::FLEUR
), ev
->time
);
455 _drag_start_x
= event_x
;
456 _drag_start_y
= event_y
;
458 _drag_rect
= new ArdourCanvas::SimpleRect(*group
);
459 _drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
461 _drag_rect
->property_y1() = midi_stream_view()->note_to_y(
462 midi_stream_view()->y_to_note(event_y
));
463 _drag_rect
->property_x2() = trackview
.editor().frame_to_pixel(event_frame
);
464 _drag_rect
->property_y2() = _drag_rect
->property_y1()
465 + floor(midi_stream_view()->note_height());
466 _drag_rect
->property_outline_what() = 0xFF;
467 _drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
468 _drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
470 _mouse_state
= AddDragging
;
476 case SelectRectDragging
: // Select drag motion
477 case AddDragging
: // Add note drag motion
481 GdkModifierType state
;
482 gdk_window_get_pointer(ev
->window
, &t_x
, &t_y
, &state
);
487 if (_mouse_state
== AddDragging
)
488 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
491 if (event_x
> _drag_start_x
)
492 _drag_rect
->property_x2() = event_x
;
494 _drag_rect
->property_x1() = event_x
;
497 if (_drag_rect
&& _mouse_state
== SelectRectDragging
) {
498 if (event_y
> _drag_start_y
)
499 _drag_rect
->property_y2() = event_y
;
501 _drag_rect
->property_y1() = event_y
;
503 update_drag_selection(_drag_start_x
, event_x
, _drag_start_y
, event_y
);
509 case SelectTouchDragging
:
521 MidiRegionView::scroll (GdkEventScroll
* ev
)
523 if (_selection
.empty()) {
527 trackview
.editor().hide_verbose_canvas_cursor ();
529 bool fine
= !Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
);
531 if (ev
->direction
== GDK_SCROLL_UP
) {
532 change_velocities (true, fine
, false);
533 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
534 change_velocities (false, fine
, false);
540 MidiRegionView::key_press (GdkEventKey
* ev
)
542 /* since GTK bindings are generally activated on press, and since
543 detectable auto-repeat is the name of the game and only sends
544 repeated presses, carry out key actions at key press, not release.
547 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
){
548 _mouse_state
= SelectTouchDragging
;
551 } else if (ev
->keyval
== GDK_Escape
) {
555 } else if (ev
->keyval
== GDK_comma
|| ev
->keyval
== GDK_period
) {
557 bool start
= (ev
->keyval
== GDK_comma
);
558 bool end
= (ev
->keyval
== GDK_period
);
559 bool shorter
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
);
560 bool fine
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
562 change_note_lengths (fine
, shorter
, start
, end
);
566 } else if (ev
->keyval
== GDK_Delete
) {
571 } else if (ev
->keyval
== GDK_Tab
) {
573 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
574 goto_previous_note ();
580 } else if (ev
->keyval
== GDK_Up
) {
582 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
583 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
585 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
586 change_velocities (true, fine
, allow_smush
);
588 transpose (true, fine
, allow_smush
);
592 } else if (ev
->keyval
== GDK_Down
) {
594 bool allow_smush
= Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
);
595 bool fine
= !Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
);
597 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::PrimaryModifier
)) {
598 change_velocities (false, fine
, allow_smush
);
600 transpose (false, fine
, allow_smush
);
604 } else if (ev
->keyval
== GDK_Left
) {
609 } else if (ev
->keyval
== GDK_Right
) {
614 } else if (ev
->keyval
== GDK_Control_L
) {
617 } else if (ev
->keyval
== GDK_r
) {
618 /* yes, this steals r */
619 if (midi_view()->midi_track()->step_editing()) {
620 midi_view()->step_edit_rest ();
629 MidiRegionView::key_release (GdkEventKey
* ev
)
631 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
639 MidiRegionView::show_list_editor ()
642 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
644 _list_editor
->present ();
647 /** Add a note to the model, and the view, at a canvas (click) coordinate.
648 * \param x horizontal position in pixels
649 * \param y vertical position in pixels
650 * \param length duration of the note in beats, which will be snapped to the grid
651 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
654 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
656 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
657 MidiStreamView
* const view
= mtv
->midi_view();
659 double note
= midi_stream_view()->y_to_note(y
);
662 assert(note
<= 127.0);
664 // Start of note in frames relative to region start
665 nframes64_t
const start_frames
= snap_frame_to_frame(trackview
.editor().pixel_to_frame(x
));
666 assert(start_frames
>= 0);
669 length
= frames_to_beats(
670 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
672 assert (length
!= 0);
675 length
= frames_to_beats (beats_to_frames (length
) - 1);
678 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
682 /* pick the highest selected channel, unless all channels are selected,
683 which is interpreted to mean channel 1 (zero)
686 for (uint16_t i
= 0; i
< 16; ++i
) {
687 if (chn_mask
& (1<<i
)) {
697 const boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
,
698 frames_to_beats(start_frames
+ _region
->start()), length
,
699 (uint8_t)note
, 0x40));
701 if (_model
->contains (new_note
)) {
705 view
->update_note_range(new_note
->note());
707 MidiModel::DiffCommand
* cmd
= _model
->new_diff_command("add note");
709 _model
->apply_command(*trackview
.session(), cmd
);
711 play_midi_note (new_note
);
715 MidiRegionView::clear_events()
720 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
721 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
726 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
731 _pgm_changes
.clear();
733 _optimization_iterator
= _events
.end();
738 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
741 content_connection
.disconnect ();
742 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
746 if (_enable_display
) {
752 MidiRegionView::start_diff_command(string name
)
754 if (!_diff_command
) {
755 _diff_command
= _model
->new_diff_command(name
);
760 MidiRegionView::diff_add_note(const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
763 _diff_command
->add(note
);
766 _marked_for_selection
.insert(note
);
769 _marked_for_velocity
.insert(note
);
774 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent
* ev
)
776 if (_diff_command
&& ev
->note()) {
777 _diff_command
->remove(ev
->note());
782 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
783 MidiModel::DiffCommand::Property property
,
787 _diff_command
->change (ev
->note(), property
, val
);
792 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
793 MidiModel::DiffCommand::Property property
,
794 Evoral::MusicalTime val
)
797 _diff_command
->change (ev
->note(), property
, val
);
802 MidiRegionView::apply_diff ()
806 if (!_diff_command
) {
810 if ((add_or_remove
= _diff_command
->adds_or_removes())) {
811 // Mark all selected notes for selection when model reloads
812 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
813 _marked_for_selection
.insert((*i
)->note());
817 _model
->apply_command(*trackview
.session(), _diff_command
);
819 midi_view()->midi_track()->playlist_modified();
822 _marked_for_selection
.clear();
825 _marked_for_velocity
.clear();
829 MidiRegionView::apply_diff_as_subcommand()
833 if (!_diff_command
) {
837 if ((add_or_remove
= _diff_command
->adds_or_removes())) {
838 // Mark all selected notes for selection when model reloads
839 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
840 _marked_for_selection
.insert((*i
)->note());
844 _model
->apply_command_as_subcommand(*trackview
.session(), _diff_command
);
846 midi_view()->midi_track()->playlist_modified();
849 _marked_for_selection
.clear();
851 _marked_for_velocity
.clear();
856 MidiRegionView::abort_command()
858 delete _diff_command
;
864 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
866 if (_optimization_iterator
!= _events
.end()) {
867 ++_optimization_iterator
;
870 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
871 return *_optimization_iterator
;
874 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
875 if ((*_optimization_iterator
)->note() == note
) {
876 return *_optimization_iterator
;
884 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
886 MidiModel::Notes notes
;
887 _model
->get_notes (notes
, op
, val
, chan_mask
);
889 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
890 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
898 MidiRegionView::redisplay_model()
900 // Don't redisplay the model if we're currently recording and displaying that
906 cerr
<< "MidiRegionView::redisplay_model called without a model" << endmsg
;
910 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
914 MidiModel::ReadLock
lock(_model
->read_lock());
916 MidiModel::Notes
& notes (_model
->notes());
917 _optimization_iterator
= _events
.begin();
919 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
921 boost::shared_ptr
<NoteType
> note (*n
);
922 CanvasNoteEvent
* cne
;
925 if (note_in_region_range (note
, visible
)) {
927 if ((cne
= find_canvas_note (note
)) != 0) {
934 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
936 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
948 add_note (note
, visible
);
953 if ((cne
= find_canvas_note (note
)) != 0) {
961 /* remove note items that are no longer valid */
963 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
964 if (!(*i
)->valid ()) {
966 i
= _events
.erase (i
);
972 _pgm_changes
.clear();
976 display_program_changes();
978 _marked_for_selection
.clear ();
979 _marked_for_velocity
.clear ();
981 /* we may have caused _events to contain things out of order (e.g. if a note
982 moved earlier or later). we don't generally need them in time order, but
983 make a note that a sort is required for those cases that require it.
990 MidiRegionView::display_program_changes()
992 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
993 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
995 for (uint8_t i
= 0; i
< 16; ++i
) {
996 if (chn_mask
& (1<<i
)) {
997 display_program_changes_on_channel (i
);
1003 MidiRegionView::display_program_changes_on_channel(uint8_t channel
)
1005 boost::shared_ptr
<Evoral::Control
> control
=
1006 _model
->control(Evoral::MIDI::ProgramChange (MidiPgmChangeAutomation
, channel
));
1012 Glib::Mutex::Lock
lock (control
->list()->lock());
1014 for (AutomationList::const_iterator event
= control
->list()->begin();
1015 event
!= control
->list()->end(); ++event
) {
1016 double event_time
= (*event
)->when
;
1017 double program_number
= floor((*event
)->value
+ 0.5);
1019 // Get current value of bank select MSB at time of the program change
1020 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, channel
, MIDI_CTL_MSB_BANK
);
1021 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1023 if (msb_control
!= 0) {
1024 msb
= uint8_t(floor(msb_control
->get_double(true, event_time
) + 0.5));
1027 // Get current value of bank select LSB at time of the program change
1028 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, channel
, MIDI_CTL_LSB_BANK
);
1029 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1031 if (lsb_control
!= 0) {
1032 lsb
= uint8_t(floor(lsb_control
->get_double(true, event_time
) + 0.5));
1035 MIDI::Name::PatchPrimaryKey
patch_key(msb
, lsb
, program_number
);
1037 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1038 MIDI::Name::MidiPatchManager::instance().find_patch(
1039 _model_name
, _custom_device_mode
, channel
, patch_key
);
1041 PCEvent
program_change(event_time
, uint8_t(program_number
), channel
);
1044 add_pgm_change(program_change
, patch
->name());
1047 // program_number is zero-based: convert to one-based
1048 snprintf(buf
, 4, "%d", int(program_number
+1));
1049 add_pgm_change(program_change
, buf
);
1055 MidiRegionView::display_sysexes()
1057 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1058 Evoral::MusicalTime time
= (*i
)->time();
1063 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1064 str
<< int((*i
)->buffer()[b
]);
1065 if (b
!= (*i
)->size() -1) {
1069 string text
= str
.str();
1071 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1073 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1075 double height
= midi_stream_view()->contents_height();
1077 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1078 new CanvasSysEx(*this, *group
, text
, height
, x
, 1.0));
1080 // Show unless program change is beyond the region bounds
1081 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1087 _sys_exes
.push_back(sysex
);
1092 MidiRegionView::~MidiRegionView ()
1094 in_destructor
= true;
1096 trackview
.editor().hide_verbose_canvas_cursor ();
1098 note_delete_connection
.disconnect ();
1100 delete _list_editor
;
1102 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1104 if (_active_notes
) {
1111 delete _diff_command
;
1115 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1117 RegionView::region_resized(what_changed
);
1119 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1120 set_duration(_region
->length(), 0);
1121 if (_enable_display
) {
1128 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1130 RegionView::reset_width_dependent_items(pixel_width
);
1131 assert(_pixel_width
== pixel_width
);
1133 if (_enable_display
) {
1139 MidiRegionView::set_height (double height
)
1141 static const double FUDGE
= 2.0;
1142 const double old_height
= _height
;
1143 RegionView::set_height(height
);
1144 _height
= height
- FUDGE
;
1146 apply_note_range(midi_stream_view()->lowest_note(),
1147 midi_stream_view()->highest_note(),
1148 height
!= old_height
+ FUDGE
);
1151 name_pixbuf
->raise_to_top();
1156 /** Apply the current note range from the stream view
1157 * by repositioning/hiding notes as necessary
1160 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1162 if (!_enable_display
) {
1166 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1170 _current_range_min
= min
;
1171 _current_range_max
= max
;
1173 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1174 CanvasNoteEvent
* event
= *i
;
1175 boost::shared_ptr
<NoteType
> note (event
->note());
1177 if (note
->note() < _current_range_min
||
1178 note
->note() > _current_range_max
) {
1184 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1186 const double y1
= midi_stream_view()->note_to_y(note
->note());
1187 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1189 cnote
->property_y1() = y1
;
1190 cnote
->property_y2() = y2
;
1192 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1194 double x
= trackview
.editor().frame_to_pixel(
1195 beats_to_frames(note
->time()) - _region
->start());
1196 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1197 double y
= midi_stream_view()->note_to_y(event
->note()->note())
1198 + ((diamond_size
-2.0) / 4.0);
1200 chit
->set_height (diamond_size
);
1201 chit
->move (x
- chit
->x1(), y
- chit
->y1());
1208 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1212 double unit_position
= _region
->position () / samples_per_unit
;
1213 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1214 MidiGhostRegion
* ghost
;
1216 if (mtv
&& mtv
->midi_view()) {
1217 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1218 to allow having midi notes on top of note lines and waveforms.
1220 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1222 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1225 ghost
->set_height ();
1226 ghost
->set_duration (_region
->length() / samples_per_unit
);
1227 ghosts
.push_back (ghost
);
1229 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1230 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1231 ghost
->add_note(note
);
1235 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1241 /** Begin tracking note state for successive calls to add_event
1244 MidiRegionView::begin_write()
1246 assert(!_active_notes
);
1247 _active_notes
= new CanvasNote
*[128];
1248 for (unsigned i
=0; i
< 128; ++i
) {
1249 _active_notes
[i
] = 0;
1254 /** Destroy note state for add_event
1257 MidiRegionView::end_write()
1259 delete[] _active_notes
;
1261 _marked_for_selection
.clear();
1262 _marked_for_velocity
.clear();
1266 /** Resolve an active MIDI note (while recording).
1269 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1271 if (midi_view()->note_mode() != Sustained
) {
1275 if (_active_notes
&& _active_notes
[note
]) {
1276 const nframes64_t end_time_frames
= beats_to_frames(end_time
);
1277 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1278 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1279 _active_notes
[note
] = 0;
1284 /** Extend active notes to rightmost edge of region (if length is changed)
1287 MidiRegionView::extend_active_notes()
1289 if (!_active_notes
) {
1293 for (unsigned i
=0; i
< 128; ++i
) {
1294 if (_active_notes
[i
]) {
1295 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1301 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1303 if (no_sound_notes
|| !trackview
.editor().sound_notes()) {
1307 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1310 route_ui
->midi_track()->write_immediate_event(
1311 note
->on_event().size(), note
->on_event().buffer());
1313 const double note_length_beats
= (note
->off_event().time() - note
->on_event().time());
1314 nframes_t note_length_ms
= beats_to_frames(note_length_beats
)
1315 * (1000 / (double)route_ui
->session()->nominal_frame_rate());
1316 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off
), note
),
1317 note_length_ms
, G_PRIORITY_DEFAULT
);
1321 MidiRegionView::play_midi_note_off(boost::shared_ptr
<NoteType
> note
)
1323 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1326 route_ui
->midi_track()->write_immediate_event(
1327 note
->off_event().size(), note
->off_event().buffer());
1333 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1335 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1337 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1338 (note_start_frames
< _region
->start());
1340 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1341 (note
->note() <= midi_stream_view()->highest_note());
1347 MidiRegionView::update_note (CanvasNote
* ev
)
1349 boost::shared_ptr
<NoteType
> note
= ev
->note();
1351 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1353 /* trim note display to not overlap the end of its region */
1354 const nframes64_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1356 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1357 const double y1
= midi_stream_view()->note_to_y(note
->note());
1358 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1360 ev
->property_x1() = x
;
1361 ev
->property_y1() = y1
;
1362 if (note
->length() > 0) {
1363 ev
->property_x2() = note_endpixel
;
1365 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1367 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1369 if (note
->length() == 0) {
1370 if (_active_notes
) {
1371 assert(note
->note() < 128);
1372 // If this note is already active there's a stuck note,
1373 // finish the old note rectangle
1374 if (_active_notes
[note
->note()]) {
1375 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1376 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1377 old_rect
->property_x2() = x
;
1378 old_rect
->property_outline_what() = (guint32
) 0xF;
1380 _active_notes
[note
->note()] = ev
;
1382 /* outline all but right edge */
1383 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1385 /* outline all edges */
1386 ev
->property_outline_what() = (guint32
) 0xF;
1391 MidiRegionView::update_hit (CanvasHit
* ev
)
1393 boost::shared_ptr
<NoteType
> note
= ev
->note();
1395 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1396 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1397 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1398 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1403 /** Add a MIDI note to the view (with length).
1405 * If in sustained mode, notes with length 0 will be considered active
1406 * notes, and resolve_note should be called when the corresponding note off
1407 * event arrives, to properly display the note.
1410 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1412 CanvasNoteEvent
* event
= 0;
1414 assert(note
->time() >= 0);
1415 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1417 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1419 if (midi_view()->note_mode() == Sustained
) {
1421 CanvasNote
* ev_rect
= new CanvasNote(*this, *group
, note
);
1423 update_note (ev_rect
);
1427 MidiGhostRegion
* gr
;
1429 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1430 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1431 gr
->add_note(ev_rect
);
1435 } else if (midi_view()->note_mode() == Percussive
) {
1437 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1439 CanvasHit
* ev_diamond
= new CanvasHit(*this, *group
, diamond_size
, note
);
1441 update_hit (ev_diamond
);
1450 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1451 note_selected(event
, true);
1454 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1455 event
->show_velocity();
1457 event
->on_channel_selection_change(_last_channel_selection
);
1458 _events
.push_back(event
);
1469 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1470 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1472 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1474 /* potentially extend region to hold new note */
1476 nframes64_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1477 nframes64_t region_end
= _region
->position() + _region
->length() - 1;
1479 if (end_frame
> region_end
) {
1480 _region
->set_length (end_frame
, this);
1483 start_diff_command (_("step add"));
1484 diff_add_note (new_note
, true, false);
1487 // last_step_edit_note = new_note;
1491 MidiRegionView::add_pgm_change(PCEvent
& program
, const string
& displaytext
)
1493 assert(program
.time
>= 0);
1495 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1496 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(program
.time
));
1498 double height
= midi_stream_view()->contents_height();
1500 boost::shared_ptr
<CanvasProgramChange
> pgm_change
= boost::shared_ptr
<CanvasProgramChange
>(
1501 new CanvasProgramChange(*this, *group
,
1506 _custom_device_mode
,
1507 program
.time
, program
.channel
, program
.value
));
1509 // Show unless program change is beyond the region bounds
1510 if (program
.time
- _region
->start() >= _region
->length() || program
.time
< _region
->start()) {
1516 _pgm_changes
.push_back(pgm_change
);
1520 MidiRegionView::get_patch_key_at(double time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1522 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, channel
, MIDI_CTL_MSB_BANK
);
1523 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1525 if (msb_control
!= 0) {
1526 msb
= int(msb_control
->get_double(true, time
));
1529 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, channel
, MIDI_CTL_LSB_BANK
);
1530 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1532 if (lsb_control
!= 0) {
1533 lsb
= lsb_control
->get_double(true, time
);
1536 Evoral::Parameter
program_change(MidiPgmChangeAutomation
, channel
, 0);
1537 boost::shared_ptr
<Evoral::Control
> program_control
= _model
->control(program_change
);
1538 double program_number
= -1.0;
1539 if (program_control
!= 0) {
1540 program_number
= program_control
->get_double(true, time
);
1543 key
.msb
= (int) floor(msb
+ 0.5);
1544 key
.lsb
= (int) floor(lsb
+ 0.5);
1545 key
.program_number
= (int) floor(program_number
+ 0.5);
1546 assert(key
.is_sane());
1551 MidiRegionView::alter_program_change(PCEvent
& old_program
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1553 // TODO: Get the real event here and alter them at the original times
1554 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, old_program
.channel
, MIDI_CTL_MSB_BANK
);
1555 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1556 if (msb_control
!= 0) {
1557 msb_control
->set_double(double(new_patch
.msb
), true, old_program
.time
);
1560 // TODO: Get the real event here and alter them at the original times
1561 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, old_program
.channel
, MIDI_CTL_LSB_BANK
);
1562 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1563 if (lsb_control
!= 0) {
1564 lsb_control
->set_double(double(new_patch
.lsb
), true, old_program
.time
);
1567 Evoral::Parameter
program_change(MidiPgmChangeAutomation
, old_program
.channel
, 0);
1568 boost::shared_ptr
<Evoral::Control
> program_control
= _model
->control(program_change
);
1570 assert(program_control
!= 0);
1571 program_control
->set_double(float(new_patch
.program_number
), true, old_program
.time
);
1573 _pgm_changes
.clear ();
1574 display_program_changes (); // XXX would be nice to limit to just old_program.channel
1578 MidiRegionView::program_selected(CanvasProgramChange
& program
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1580 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1581 alter_program_change(program_change_event
, new_patch
);
1585 MidiRegionView::previous_program(CanvasProgramChange
& program
)
1587 if (program
.program() < 127) {
1588 MIDI::Name::PatchPrimaryKey key
;
1589 get_patch_key_at(program
.event_time(), program
.channel(), key
);
1590 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1592 key
.program_number
++;
1593 alter_program_change(program_change_event
, key
);
1598 MidiRegionView::next_program(CanvasProgramChange
& program
)
1600 if (program
.program() > 0) {
1601 MIDI::Name::PatchPrimaryKey key
;
1602 get_patch_key_at(program
.event_time(), program
.channel(), key
);
1603 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1605 key
.program_number
--;
1606 alter_program_change(program_change_event
, key
);
1611 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1613 if (_selection
.empty()) {
1617 if (_selection
.erase (cne
) > 0) {
1618 cerr
<< "Erased a CNE from selection\n";
1623 MidiRegionView::delete_selection()
1625 if (_selection
.empty()) {
1629 start_diff_command (_("delete selection"));
1631 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1632 if ((*i
)->selected()) {
1633 _diff_command
->remove((*i
)->note());
1643 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1645 start_diff_command (_("delete note"));
1646 _diff_command
->remove (n
);
1649 trackview
.editor().hide_verbose_canvas_cursor ();
1653 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1655 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1656 if ((*i
)->selected() && (*i
) != ev
) {
1657 (*i
)->set_selected(false);
1658 (*i
)->hide_velocity();
1666 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1668 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1671 Selection::iterator tmp
= i
;
1674 (*i
)->set_selected (false);
1675 _selection
.erase (i
);
1684 /* don't bother with removing this regionview from the editor selection,
1685 since we're about to add another note, and thus put/keep this
1686 regionview in the editor selection.
1689 if (!ev
->selected()) {
1690 add_to_selection (ev
);
1695 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1697 uint8_t low_note
= 127;
1698 uint8_t high_note
= 0;
1699 MidiModel::Notes
& notes (_model
->notes());
1700 _optimization_iterator
= _events
.begin();
1706 if (extend
&& _selection
.empty()) {
1712 /* scan existing selection to get note range */
1714 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1715 if ((*i
)->note()->note() < low_note
) {
1716 low_note
= (*i
)->note()->note();
1718 if ((*i
)->note()->note() > high_note
) {
1719 high_note
= (*i
)->note()->note();
1723 low_note
= min (low_note
, notenum
);
1724 high_note
= max (high_note
, notenum
);
1727 no_sound_notes
= true;
1729 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1731 boost::shared_ptr
<NoteType
> note (*n
);
1732 CanvasNoteEvent
* cne
;
1733 bool select
= false;
1735 if (((1 << note
->channel()) & channel_mask
) != 0) {
1737 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1740 } else if (note
->note() == notenum
) {
1746 if ((cne
= find_canvas_note (note
)) != 0) {
1747 // extend is false because we've taken care of it,
1748 // since it extends by time range, not pitch.
1749 note_selected (cne
, add
, false);
1753 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1757 no_sound_notes
= false;
1761 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
1763 MidiModel::Notes
& notes (_model
->notes());
1764 _optimization_iterator
= _events
.begin();
1766 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1768 boost::shared_ptr
<NoteType
> note (*n
);
1769 CanvasNoteEvent
* cne
;
1771 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
1772 if ((cne
= find_canvas_note (note
)) != 0) {
1773 if (cne
->selected()) {
1774 note_deselected (cne
);
1776 note_selected (cne
, true, false);
1784 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
1787 clear_selection_except(ev
);
1792 if (!ev
->selected()) {
1793 add_to_selection (ev
);
1797 /* find end of latest note selected, select all between that and the start of "ev" */
1799 Evoral::MusicalTime earliest
= DBL_MAX
;
1800 Evoral::MusicalTime latest
= 0;
1802 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1803 if ((*i
)->note()->end_time() > latest
) {
1804 latest
= (*i
)->note()->end_time();
1806 if ((*i
)->note()->time() < earliest
) {
1807 earliest
= (*i
)->note()->time();
1811 if (ev
->note()->end_time() > latest
) {
1812 latest
= ev
->note()->end_time();
1815 if (ev
->note()->time() < earliest
) {
1816 earliest
= ev
->note()->time();
1819 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1821 /* find notes entirely within OR spanning the earliest..latest range */
1823 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
1824 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
1825 add_to_selection (*i
);
1829 /* if events were guaranteed to be time sorted, we could do this.
1830 but as of sept 10th 2009, they no longer are.
1833 if ((*i
)->note()->time() > latest
) {
1842 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
1844 remove_from_selection (ev
);
1848 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
1858 // TODO: Make this faster by storing the last updated selection rect, and only
1859 // adjusting things that are in the area that appears/disappeared.
1860 // We probably need a tree to be able to find events in O(log(n)) time.
1862 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1864 /* check if any corner of the note is inside the rect
1867 1) this is computing "touched by", not "contained by" the rect.
1868 2) this does not require that events be sorted in time.
1871 const double ix1
= (*i
)->x1();
1872 const double ix2
= (*i
)->x2();
1873 const double iy1
= (*i
)->y1();
1874 const double iy2
= (*i
)->y2();
1876 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
1877 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
1878 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
1879 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
1882 if (!(*i
)->selected()) {
1883 add_to_selection (*i
);
1885 } else if ((*i
)->selected()) {
1886 // Not inside rectangle
1887 remove_from_selection (*i
);
1893 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
1895 Selection::iterator i
= _selection
.find (ev
);
1897 if (i
!= _selection
.end()) {
1898 _selection
.erase (i
);
1901 ev
->set_selected (false);
1902 ev
->hide_velocity ();
1904 if (_selection
.empty()) {
1905 PublicEditor
& editor (trackview
.editor());
1906 editor
.get_selection().remove (this);
1911 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
1913 bool add_mrv_selection
= false;
1915 if (_selection
.empty()) {
1916 add_mrv_selection
= true;
1919 if (_selection
.insert (ev
).second
) {
1920 ev
->set_selected (true);
1921 play_midi_note ((ev
)->note());
1924 if (add_mrv_selection
) {
1925 PublicEditor
& editor (trackview
.editor());
1926 editor
.get_selection().add (this);
1931 MidiRegionView::move_selection(double dx
, double dy
)
1933 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1934 (*i
)->move_event(dx
, dy
);
1939 MidiRegionView::note_dropped(CanvasNoteEvent
*, double dt
, int8_t dnote
)
1941 assert (!_selection
.empty());
1943 uint8_t lowest_note_in_selection
= 127;
1944 uint8_t highest_note_in_selection
= 0;
1945 uint8_t highest_note_difference
= 0;
1947 // find highest and lowest notes first
1949 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1950 uint8_t pitch
= (*i
)->note()->note();
1951 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
1952 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
1956 cerr << "dnote: " << (int) dnote << endl;
1957 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1958 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1959 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1960 << int(highest_note_in_selection) << endl;
1961 cerr << "selection size: " << _selection.size() << endl;
1962 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1965 // Make sure the note pitch does not exceed the MIDI standard range
1966 if (highest_note_in_selection
+ dnote
> 127) {
1967 highest_note_difference
= highest_note_in_selection
- 127;
1970 start_diff_command(_("move notes"));
1972 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
1974 nframes64_t start_frames
= beats_to_frames((*i
)->note()->time());
1977 start_frames
+= snap_frame_to_frame(trackview
.editor().pixel_to_frame(dt
));
1979 start_frames
-= snap_frame_to_frame(trackview
.editor().pixel_to_frame(-dt
));
1982 Evoral::MusicalTime new_time
= frames_to_beats(start_frames
);
1988 diff_add_change (*i
, MidiModel::DiffCommand::StartTime
, new_time
);
1990 uint8_t original_pitch
= (*i
)->note()->note();
1991 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
1993 // keep notes in standard midi range
1994 clamp_to_0_127(new_pitch
);
1996 // keep original pitch if note is dragged outside valid midi range
1997 if ((original_pitch
!= 0 && new_pitch
== 0)
1998 || (original_pitch
!= 127 && new_pitch
== 127)) {
1999 new_pitch
= original_pitch
;
2002 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
2003 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
2005 diff_add_change (*i
, MidiModel::DiffCommand::NoteNumber
, new_pitch
);
2010 // care about notes being moved beyond the upper/lower bounds on the canvas
2011 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2012 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2013 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2018 MidiRegionView::snap_pixel_to_frame(double x
)
2020 PublicEditor
& editor
= trackview
.editor();
2021 // x is region relative, convert it to global absolute frames
2022 nframes64_t frame
= editor
.pixel_to_frame(x
) + _region
->position();
2023 editor
.snap_to(frame
);
2024 return frame
- _region
->position(); // convert back to region relative
2028 MidiRegionView::snap_frame_to_frame(nframes64_t x
)
2030 PublicEditor
& editor
= trackview
.editor();
2031 // x is region relative, convert it to global absolute frames
2032 nframes64_t frame
= x
+ _region
->position();
2033 editor
.snap_to(frame
);
2034 return frame
- _region
->position(); // convert back to region relative
2038 MidiRegionView::snap_to_pixel(double x
)
2040 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2044 MidiRegionView::get_position_pixels()
2046 nframes64_t region_frame
= get_position();
2047 return trackview
.editor().frame_to_pixel(region_frame
);
2051 MidiRegionView::get_end_position_pixels()
2053 nframes64_t frame
= get_position() + get_duration ();
2054 return trackview
.editor().frame_to_pixel(frame
);
2058 MidiRegionView::beats_to_frames(double beats
) const
2060 return _time_converter
.to(beats
);
2064 MidiRegionView::frames_to_beats(nframes64_t frames
) const
2066 return _time_converter
.from(frames
);
2070 MidiRegionView::begin_resizing (bool /*at_front*/)
2072 _resize_data
.clear();
2074 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2075 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2077 // only insert CanvasNotes into the map
2079 NoteResizeData
*resize_data
= new NoteResizeData();
2080 resize_data
->canvas_note
= note
;
2082 // create a new SimpleRect from the note which will be the resize preview
2083 SimpleRect
*resize_rect
= new SimpleRect(
2084 *group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2086 // calculate the colors: get the color settings
2087 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2088 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2091 // make the resize preview notes more transparent and bright
2092 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2094 // calculate color based on note velocity
2095 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2096 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2100 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2101 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2103 resize_data
->resize_rect
= resize_rect
;
2104 _resize_data
.push_back(resize_data
);
2109 /** Update resizing notes while user drags.
2110 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2111 * @param at_front which end of the note (true == note on, false == note off)
2112 * @param delta_x change in mouse position since the start of the drag
2113 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2114 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2115 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2116 * as the \a primary note.
2119 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote
* primary
, bool at_front
, double delta_x
, bool relative
)
2121 bool cursor_set
= false;
2123 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2124 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2125 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2130 current_x
= canvas_note
->x1() + delta_x
;
2132 current_x
= primary
->x1() + delta_x
;
2136 current_x
= canvas_note
->x2() + delta_x
;
2138 current_x
= primary
->x2() + delta_x
;
2143 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2144 resize_rect
->property_x2() = canvas_note
->x2();
2146 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2147 resize_rect
->property_x1() = canvas_note
->x1();
2153 beats
= snap_pixel_to_frame (current_x
);
2154 beats
= frames_to_beats (beats
);
2159 if (beats
< canvas_note
->note()->end_time()) {
2160 len
= canvas_note
->note()->time() - beats
;
2161 len
+= canvas_note
->note()->length();
2166 if (beats
>= canvas_note
->note()->end_time()) {
2167 len
= beats
- canvas_note
->note()->time();
2174 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2175 trackview
.editor().show_verbose_canvas_cursor_with (buf
);
2184 /** Finish resizing notes when the user releases the mouse button.
2185 * Parameters the same as for \a update_resizing().
2188 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote
* primary
, bool at_front
, double delta_x
, bool relative
)
2190 start_diff_command(_("resize notes"));
2192 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2193 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2194 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2199 current_x
= canvas_note
->x1() + delta_x
;
2201 current_x
= primary
->x1() + delta_x
;
2205 current_x
= canvas_note
->x2() + delta_x
;
2207 current_x
= primary
->x2() + delta_x
;
2211 current_x
= snap_pixel_to_frame (current_x
);
2212 current_x
= frames_to_beats (current_x
);
2214 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2215 diff_add_change (canvas_note
, MidiModel::DiffCommand::StartTime
, current_x
);
2217 double len
= canvas_note
->note()->time() - current_x
;
2218 len
+= canvas_note
->note()->length();
2221 /* XXX convert to beats */
2222 diff_add_change (canvas_note
, MidiModel::DiffCommand::Length
, len
);
2227 double len
= current_x
- canvas_note
->note()->time();
2230 /* XXX convert to beats */
2231 diff_add_change (canvas_note
, MidiModel::DiffCommand::Length
, len
);
2239 _resize_data
.clear();
2244 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2246 uint8_t new_velocity
;
2249 new_velocity
= event
->note()->velocity() + velocity
;
2250 clamp_to_0_127(new_velocity
);
2252 new_velocity
= velocity
;
2255 event
->set_selected (event
->selected()); // change color
2257 diff_add_change (event
, MidiModel::DiffCommand::Velocity
, new_velocity
);
2261 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2266 new_note
= event
->note()->note() + note
;
2271 clamp_to_0_127 (new_note
);
2272 diff_add_change (event
, MidiModel::DiffCommand::NoteNumber
, new_note
);
2276 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2278 bool change_start
= false;
2279 bool change_length
= false;
2280 Evoral::MusicalTime new_start
= 0;
2281 Evoral::MusicalTime new_length
= 0;
2283 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2285 front_delta: if positive - move the start of the note later in time (shortening it)
2286 if negative - move the start of the note earlier in time (lengthening it)
2288 end_delta: if positive - move the end of the note later in time (lengthening it)
2289 if negative - move the end of the note earlier in time (shortening it)
2293 if (front_delta
< 0) {
2295 if (event
->note()->time() < -front_delta
) {
2298 new_start
= event
->note()->time() + front_delta
; // moves earlier
2301 /* start moved toward zero, so move the end point out to where it used to be.
2302 Note that front_delta is negative, so this increases the length.
2305 new_length
= event
->note()->length() - front_delta
;
2306 change_start
= true;
2307 change_length
= true;
2311 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2313 if (new_pos
< event
->note()->end_time()) {
2314 new_start
= event
->note()->time() + front_delta
;
2315 /* start moved toward the end, so move the end point back to where it used to be */
2316 new_length
= event
->note()->length() - front_delta
;
2317 change_start
= true;
2318 change_length
= true;
2325 bool can_change
= true;
2326 if (end_delta
< 0) {
2327 if (event
->note()->length() < -end_delta
) {
2333 new_length
= event
->note()->length() + end_delta
;
2334 change_length
= true;
2339 diff_add_change (event
, MidiModel::DiffCommand::StartTime
, new_start
);
2342 if (change_length
) {
2343 diff_add_change (event
, MidiModel::DiffCommand::Length
, new_length
);
2348 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2350 Evoral::MusicalTime new_time
;
2354 if (event
->note()->time() < -delta
) {
2357 new_time
= event
->note()->time() + delta
;
2360 new_time
= event
->note()->time() + delta
;
2366 diff_add_change (event
, MidiModel::DiffCommand::StartTime
, new_time
);
2370 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2374 if (_selection
.empty()) {
2389 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2390 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2396 start_diff_command(_("change velocities"));
2398 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2399 Selection::iterator next
= i
;
2401 change_note_velocity (*i
, delta
, true);
2407 if (!_selection
.empty()) {
2409 snprintf (buf
, sizeof (buf
), "Vel %d",
2410 (int) (*_selection
.begin())->note()->velocity());
2411 trackview
.editor().show_verbose_canvas_cursor_with (buf
);
2417 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2419 if (_selection
.empty()) {
2436 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2438 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2442 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2449 start_diff_command (_("transpose"));
2451 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2452 Selection::iterator next
= i
;
2454 change_note_note (*i
, delta
, true);
2462 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, bool start
, bool end
)
2464 Evoral::MusicalTime delta
;
2469 /* grab the current grid distance */
2471 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2473 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2474 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2483 start_diff_command (_("change note lengths"));
2485 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2486 Selection::iterator next
= i
;
2489 /* note the negation of the delta for start */
2491 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2500 MidiRegionView::nudge_notes (bool forward
)
2502 if (_selection
.empty()) {
2506 /* pick a note as the point along the timeline to get the nudge distance.
2507 its not necessarily the earliest note, so we may want to pull the notes out
2508 into a vector and sort before using the first one.
2511 nframes64_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2513 nframes64_t distance
;
2515 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2517 /* grid is off - use nudge distance */
2519 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2525 nframes64_t next_pos
= ref_point
;
2528 /* XXX need check on max_frames, but that needs max_frames64 or something */
2531 if (next_pos
== 0) {
2537 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2538 distance
= ref_point
- next_pos
;
2541 if (distance
== 0) {
2545 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2551 start_diff_command (_("nudge"));
2553 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2554 Selection::iterator next
= i
;
2556 change_note_time (*i
, delta
, true);
2564 MidiRegionView::change_channel(uint8_t channel
)
2566 start_diff_command(_("change channel"));
2567 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2568 diff_add_change (*i
, MidiModel::DiffCommand::Channel
, channel
);
2576 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2578 if (_mouse_state
== SelectTouchDragging
) {
2579 note_selected (ev
, true);
2582 show_verbose_canvas_cursor (ev
->note ());
2586 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
* note
)
2588 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2589 (*i
)->hide_velocity ();
2592 trackview
.editor().hide_verbose_canvas_cursor ();
2596 MidiRegionView::switch_source(boost::shared_ptr
<Source
> src
)
2598 boost::shared_ptr
<MidiSource
> msrc
= boost::dynamic_pointer_cast
<MidiSource
>(src
);
2600 display_model(msrc
->model());
2604 MidiRegionView::set_frame_color()
2607 if (_selected
&& should_show_selection
) {
2608 frame
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2610 frame
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2616 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2620 case FilterChannels
:
2621 _force_channel
= -1;
2624 _force_channel
= mask
;
2625 mask
= 0xFFFF; // Show all notes as active (below)
2628 // Update notes for selection
2629 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2630 (*i
)->on_channel_selection_change(mask
);
2633 _last_channel_selection
= mask
;
2637 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2639 _model_name
= model
;
2640 _custom_device_mode
= custom_device_mode
;
2645 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
2647 if (_selection
.empty()) {
2651 PublicEditor
& editor (trackview
.editor());
2656 editor
.get_cut_buffer().add (selection_as_cut_buffer());
2664 start_diff_command();
2666 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2672 diff_remove_note (*i
);
2682 MidiRegionView::selection_as_cut_buffer () const
2686 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2687 NoteType
* n
= (*i
)->note().get();
2688 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
2691 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
2698 MidiRegionView::paste (nframes64_t pos
, float times
, const MidiCutBuffer
& mcb
)
2704 start_diff_command (_("paste"));
2706 Evoral::MusicalTime beat_delta
;
2707 Evoral::MusicalTime paste_pos_beats
;
2708 Evoral::MusicalTime duration
;
2709 Evoral::MusicalTime end_point
= 0;
2711 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
2712 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
2713 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
2714 paste_pos_beats
= 0;
2716 _selection
.clear ();
2718 for (int n
= 0; n
< (int) times
; ++n
) {
2720 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
2722 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
2723 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
2725 /* make all newly added notes selected */
2727 diff_add_note (copied_note
, true);
2728 end_point
= copied_note
->end_time();
2731 paste_pos_beats
+= duration
;
2734 /* if we pasted past the current end of the region, extend the region */
2736 nframes64_t end_frame
= _region
->position() + beats_to_frames (end_point
);
2737 nframes64_t region_end
= _region
->position() + _region
->length() - 1;
2739 if (end_frame
> region_end
) {
2741 trackview
.session()->begin_reversible_command (_("paste"));
2743 _region
->clear_history ();
2744 _region
->set_length (end_frame
, this);
2745 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
2751 struct EventNoteTimeEarlyFirstComparator
{
2752 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
2753 return a
->note()->time() < b
->note()->time();
2758 MidiRegionView::time_sort_events ()
2760 if (!_sort_needed
) {
2764 EventNoteTimeEarlyFirstComparator cmp
;
2767 _sort_needed
= false;
2771 MidiRegionView::goto_next_note ()
2773 // nframes64_t pos = -1;
2774 bool use_next
= false;
2776 if (_events
.back()->selected()) {
2780 time_sort_events ();
2782 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2783 if ((*i
)->selected()) {
2786 } else if (use_next
) {
2788 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2793 /* use the first one */
2795 unique_select (_events
.front());
2800 MidiRegionView::goto_previous_note ()
2802 // nframes64_t pos = -1;
2803 bool use_next
= false;
2805 if (_events
.front()->selected()) {
2809 time_sort_events ();
2811 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
2812 if ((*i
)->selected()) {
2815 } else if (use_next
) {
2817 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2822 /* use the last one */
2824 unique_select (*(_events
.rbegin()));
2828 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
2830 bool had_selected
= false;
2832 time_sort_events ();
2834 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2835 if ((*i
)->selected()) {
2836 selected
.insert ((*i
)->note());
2837 had_selected
= true;
2841 if (allow_all_if_none_selected
&& !had_selected
) {
2842 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2843 selected
.insert ((*i
)->note());
2849 MidiRegionView::update_ghost_note (double x
, double y
)
2855 nframes64_t f
= trackview
.editor().pixel_to_frame (x
) + _region
->position ();
2856 trackview
.editor().snap_to (f
);
2857 f
-= _region
->position ();
2860 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
2865 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
2867 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
2868 _ghost_note
->note()->set_length (length
);
2869 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
2871 update_note (_ghost_note
);
2873 show_verbose_canvas_cursor (_ghost_note
->note ());
2877 MidiRegionView::create_ghost_note (double x
, double y
)
2882 boost::shared_ptr
<NoteType
> g (new NoteType
);
2883 _ghost_note
= new NoEventCanvasNote (*this, *group
, g
);
2884 update_ghost_note (x
, y
);
2885 _ghost_note
->show ();
2890 show_verbose_canvas_cursor (_ghost_note
->note ());
2894 MidiRegionView::snap_changed ()
2900 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
2904 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr
<NoteType
> n
) const
2907 snprintf (buf
, sizeof (buf
), "%s (%d)\nVel %d",
2908 Evoral::midi_note_name (n
->note()).c_str(),
2910 (int) n
->velocity());
2911 trackview
.editor().show_verbose_canvas_cursor_with (buf
);
2915 MidiRegionView::drop_down_keys ()
2917 _mouse_state
= None
;
2921 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double x
, double y
)
2923 double note
= midi_stream_view()->y_to_note(y
);
2925 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
2927 cerr
<< "Selecting by position\n";
2929 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
2931 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
2932 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
2933 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
2934 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
2939 bool add_mrv_selection
= false;
2941 if (_selection
.empty()) {
2942 add_mrv_selection
= true;
2945 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
2946 if (_selection
.insert (*i
).second
) {
2947 (*i
)->set_selected (true);
2951 if (add_mrv_selection
) {
2952 PublicEditor
& editor (trackview
.editor());
2953 editor
.get_selection().add (this);
2958 MidiRegionView::color_handler ()
2960 RegionView::color_handler ();
2962 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2963 (*i
)->set_selected ((*i
)->selected()); // will change color
2966 /* XXX probably more to do here */
2970 MidiRegionView::enable_display (bool yn
)
2972 RegionView::enable_display (yn
);