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
) {
623 MidiRegionView::key_release (GdkEventKey
* ev
)
625 if (ev
->keyval
== GDK_Alt_L
|| ev
->keyval
== GDK_Alt_R
) {
633 MidiRegionView::show_list_editor ()
636 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
638 _list_editor
->present ();
641 /** Add a note to the model, and the view, at a canvas (click) coordinate.
642 * \param x horizontal position in pixels
643 * \param y vertical position in pixels
644 * \param length duration of the note in beats, which will be snapped to the grid
645 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
648 MidiRegionView::create_note_at(double x
, double y
, double length
, bool sh
)
650 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
651 MidiStreamView
* const view
= mtv
->midi_view();
653 double note
= midi_stream_view()->y_to_note(y
);
656 assert(note
<= 127.0);
658 // Start of note in frames relative to region start
659 nframes64_t
const start_frames
= snap_frame_to_frame(trackview
.editor().pixel_to_frame(x
));
660 assert(start_frames
>= 0);
663 length
= frames_to_beats(
664 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
666 assert (length
!= 0);
669 length
= frames_to_beats (beats_to_frames (length
) - 1);
672 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
676 /* pick the highest selected channel, unless all channels are selected,
677 which is interpreted to mean channel 1 (zero)
680 for (uint16_t i
= 0; i
< 16; ++i
) {
681 if (chn_mask
& (1<<i
)) {
691 const boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
,
692 frames_to_beats(start_frames
+ _region
->start()), length
,
693 (uint8_t)note
, 0x40));
695 if (_model
->contains (new_note
)) {
699 view
->update_note_range(new_note
->note());
701 MidiModel::DiffCommand
* cmd
= _model
->new_diff_command("add note");
703 _model
->apply_command(*trackview
.session(), cmd
);
705 play_midi_note (new_note
);
709 MidiRegionView::clear_events()
714 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
715 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
720 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
725 _pgm_changes
.clear();
727 _optimization_iterator
= _events
.end();
732 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
735 content_connection
.disconnect ();
736 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
740 if (_enable_display
) {
746 MidiRegionView::start_diff_command(string name
)
748 if (!_diff_command
) {
749 _diff_command
= _model
->new_diff_command(name
);
754 MidiRegionView::diff_add_note(const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
757 _diff_command
->add(note
);
760 _marked_for_selection
.insert(note
);
763 _marked_for_velocity
.insert(note
);
768 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent
* ev
)
770 if (_diff_command
&& ev
->note()) {
771 _diff_command
->remove(ev
->note());
776 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
777 MidiModel::DiffCommand::Property property
,
781 _diff_command
->change (ev
->note(), property
, val
);
786 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
787 MidiModel::DiffCommand::Property property
,
788 Evoral::MusicalTime val
)
791 _diff_command
->change (ev
->note(), property
, val
);
796 MidiRegionView::apply_diff ()
800 if (!_diff_command
) {
804 if ((add_or_remove
= _diff_command
->adds_or_removes())) {
805 // Mark all selected notes for selection when model reloads
806 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
807 _marked_for_selection
.insert((*i
)->note());
811 _model
->apply_command(*trackview
.session(), _diff_command
);
813 midi_view()->midi_track()->playlist_modified();
816 _marked_for_selection
.clear();
819 _marked_for_velocity
.clear();
823 MidiRegionView::apply_diff_as_subcommand()
827 if (!_diff_command
) {
831 if ((add_or_remove
= _diff_command
->adds_or_removes())) {
832 // Mark all selected notes for selection when model reloads
833 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
834 _marked_for_selection
.insert((*i
)->note());
838 _model
->apply_command_as_subcommand(*trackview
.session(), _diff_command
);
840 midi_view()->midi_track()->playlist_modified();
843 _marked_for_selection
.clear();
845 _marked_for_velocity
.clear();
850 MidiRegionView::abort_command()
852 delete _diff_command
;
858 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
860 if (_optimization_iterator
!= _events
.end()) {
861 ++_optimization_iterator
;
864 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
865 return *_optimization_iterator
;
868 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
869 if ((*_optimization_iterator
)->note() == note
) {
870 return *_optimization_iterator
;
878 MidiRegionView::get_events (Events
& e
, Evoral::Sequence
<Evoral::MusicalTime
>::NoteOperator op
, uint8_t val
, int chan_mask
)
880 MidiModel::Notes notes
;
881 _model
->get_notes (notes
, op
, val
, chan_mask
);
883 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
884 CanvasNoteEvent
* cne
= find_canvas_note (*n
);
892 MidiRegionView::redisplay_model()
894 // Don't redisplay the model if we're currently recording and displaying that
900 cerr
<< "MidiRegionView::redisplay_model called without a model" << endmsg
;
904 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
908 MidiModel::ReadLock
lock(_model
->read_lock());
910 MidiModel::Notes
& notes (_model
->notes());
911 _optimization_iterator
= _events
.begin();
913 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
915 boost::shared_ptr
<NoteType
> note (*n
);
916 CanvasNoteEvent
* cne
;
919 if (note_in_region_range (note
, visible
)) {
921 if ((cne
= find_canvas_note (note
)) != 0) {
928 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
930 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
942 add_note (note
, visible
);
947 if ((cne
= find_canvas_note (note
)) != 0) {
955 /* remove note items that are no longer valid */
957 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
958 if (!(*i
)->valid ()) {
960 i
= _events
.erase (i
);
966 _pgm_changes
.clear();
970 display_program_changes();
972 _marked_for_selection
.clear ();
973 _marked_for_velocity
.clear ();
975 /* we may have caused _events to contain things out of order (e.g. if a note
976 moved earlier or later). we don't generally need them in time order, but
977 make a note that a sort is required for those cases that require it.
984 MidiRegionView::display_program_changes()
986 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
987 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
989 for (uint8_t i
= 0; i
< 16; ++i
) {
990 if (chn_mask
& (1<<i
)) {
991 display_program_changes_on_channel (i
);
997 MidiRegionView::display_program_changes_on_channel(uint8_t channel
)
999 boost::shared_ptr
<Evoral::Control
> control
=
1000 _model
->control(Evoral::MIDI::ProgramChange (MidiPgmChangeAutomation
, channel
));
1006 Glib::Mutex::Lock
lock (control
->list()->lock());
1008 for (AutomationList::const_iterator event
= control
->list()->begin();
1009 event
!= control
->list()->end(); ++event
) {
1010 double event_time
= (*event
)->when
;
1011 double program_number
= floor((*event
)->value
+ 0.5);
1013 // Get current value of bank select MSB at time of the program change
1014 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, channel
, MIDI_CTL_MSB_BANK
);
1015 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1017 if (msb_control
!= 0) {
1018 msb
= uint8_t(floor(msb_control
->get_double(true, event_time
) + 0.5));
1021 // Get current value of bank select LSB at time of the program change
1022 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, channel
, MIDI_CTL_LSB_BANK
);
1023 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1025 if (lsb_control
!= 0) {
1026 lsb
= uint8_t(floor(lsb_control
->get_double(true, event_time
) + 0.5));
1029 MIDI::Name::PatchPrimaryKey
patch_key(msb
, lsb
, program_number
);
1031 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1032 MIDI::Name::MidiPatchManager::instance().find_patch(
1033 _model_name
, _custom_device_mode
, channel
, patch_key
);
1035 PCEvent
program_change(event_time
, uint8_t(program_number
), channel
);
1038 add_pgm_change(program_change
, patch
->name());
1041 // program_number is zero-based: convert to one-based
1042 snprintf(buf
, 4, "%d", int(program_number
+1));
1043 add_pgm_change(program_change
, buf
);
1049 MidiRegionView::display_sysexes()
1051 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
1052 Evoral::MusicalTime time
= (*i
)->time();
1057 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
1058 str
<< int((*i
)->buffer()[b
]);
1059 if (b
!= (*i
)->size() -1) {
1063 string text
= str
.str();
1065 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1067 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
1069 double height
= midi_stream_view()->contents_height();
1071 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
1072 new CanvasSysEx(*this, *group
, text
, height
, x
, 1.0));
1074 // Show unless program change is beyond the region bounds
1075 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
1081 _sys_exes
.push_back(sysex
);
1086 MidiRegionView::~MidiRegionView ()
1088 in_destructor
= true;
1090 trackview
.editor().hide_verbose_canvas_cursor ();
1092 note_delete_connection
.disconnect ();
1094 delete _list_editor
;
1096 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1098 if (_active_notes
) {
1105 delete _diff_command
;
1109 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
1111 RegionView::region_resized(what_changed
);
1113 if (what_changed
.contains (ARDOUR::Properties::position
)) {
1114 set_duration(_region
->length(), 0);
1115 if (_enable_display
) {
1122 MidiRegionView::reset_width_dependent_items (double pixel_width
)
1124 RegionView::reset_width_dependent_items(pixel_width
);
1125 assert(_pixel_width
== pixel_width
);
1127 if (_enable_display
) {
1133 MidiRegionView::set_height (double height
)
1135 static const double FUDGE
= 2.0;
1136 const double old_height
= _height
;
1137 RegionView::set_height(height
);
1138 _height
= height
- FUDGE
;
1140 apply_note_range(midi_stream_view()->lowest_note(),
1141 midi_stream_view()->highest_note(),
1142 height
!= old_height
+ FUDGE
);
1145 name_pixbuf
->raise_to_top();
1150 /** Apply the current note range from the stream view
1151 * by repositioning/hiding notes as necessary
1154 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1156 if (!_enable_display
) {
1160 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1164 _current_range_min
= min
;
1165 _current_range_max
= max
;
1167 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1168 CanvasNoteEvent
* event
= *i
;
1169 boost::shared_ptr
<NoteType
> note (event
->note());
1171 if (note
->note() < _current_range_min
||
1172 note
->note() > _current_range_max
) {
1178 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1180 const double y1
= midi_stream_view()->note_to_y(note
->note());
1181 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1183 cnote
->property_y1() = y1
;
1184 cnote
->property_y2() = y2
;
1186 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1188 double x
= trackview
.editor().frame_to_pixel(
1189 beats_to_frames(note
->time()) - _region
->start());
1190 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1191 double y
= midi_stream_view()->note_to_y(event
->note()->note())
1192 + ((diamond_size
-2.0) / 4.0);
1194 chit
->set_height (diamond_size
);
1195 chit
->move (x
- chit
->x1(), y
- chit
->y1());
1202 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1206 double unit_position
= _region
->position () / samples_per_unit
;
1207 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1208 MidiGhostRegion
* ghost
;
1210 if (mtv
&& mtv
->midi_view()) {
1211 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1212 to allow having midi notes on top of note lines and waveforms.
1214 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1216 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1219 ghost
->set_height ();
1220 ghost
->set_duration (_region
->length() / samples_per_unit
);
1221 ghosts
.push_back (ghost
);
1223 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1224 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1225 ghost
->add_note(note
);
1229 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1235 /** Begin tracking note state for successive calls to add_event
1238 MidiRegionView::begin_write()
1240 assert(!_active_notes
);
1241 _active_notes
= new CanvasNote
*[128];
1242 for (unsigned i
=0; i
< 128; ++i
) {
1243 _active_notes
[i
] = 0;
1248 /** Destroy note state for add_event
1251 MidiRegionView::end_write()
1253 delete[] _active_notes
;
1255 _marked_for_selection
.clear();
1256 _marked_for_velocity
.clear();
1260 /** Resolve an active MIDI note (while recording).
1263 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1265 if (midi_view()->note_mode() != Sustained
) {
1269 if (_active_notes
&& _active_notes
[note
]) {
1270 const nframes64_t end_time_frames
= beats_to_frames(end_time
);
1271 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1272 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1273 _active_notes
[note
] = 0;
1278 /** Extend active notes to rightmost edge of region (if length is changed)
1281 MidiRegionView::extend_active_notes()
1283 if (!_active_notes
) {
1287 for (unsigned i
=0; i
< 128; ++i
) {
1288 if (_active_notes
[i
]) {
1289 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1295 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1297 if (no_sound_notes
|| !trackview
.editor().sound_notes()) {
1301 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1304 route_ui
->midi_track()->write_immediate_event(
1305 note
->on_event().size(), note
->on_event().buffer());
1307 const double note_length_beats
= (note
->off_event().time() - note
->on_event().time());
1308 nframes_t note_length_ms
= beats_to_frames(note_length_beats
)
1309 * (1000 / (double)route_ui
->session()->nominal_frame_rate());
1310 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off
), note
),
1311 note_length_ms
, G_PRIORITY_DEFAULT
);
1315 MidiRegionView::play_midi_note_off(boost::shared_ptr
<NoteType
> note
)
1317 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1320 route_ui
->midi_track()->write_immediate_event(
1321 note
->off_event().size(), note
->off_event().buffer());
1327 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1329 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1331 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1332 (note_start_frames
< _region
->start());
1334 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1335 (note
->note() <= midi_stream_view()->highest_note());
1341 MidiRegionView::update_note (CanvasNote
* ev
)
1343 boost::shared_ptr
<NoteType
> note
= ev
->note();
1345 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1347 /* trim note display to not overlap the end of its region */
1348 const nframes64_t note_end_frames
= min (beats_to_frames (note
->end_time()), _region
->start() + _region
->length());
1350 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1351 const double y1
= midi_stream_view()->note_to_y(note
->note());
1352 const double note_endpixel
= trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1354 ev
->property_x1() = x
;
1355 ev
->property_y1() = y1
;
1356 if (note
->length() > 0) {
1357 ev
->property_x2() = note_endpixel
;
1359 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1361 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1363 if (note
->length() == 0) {
1364 if (_active_notes
) {
1365 assert(note
->note() < 128);
1366 // If this note is already active there's a stuck note,
1367 // finish the old note rectangle
1368 if (_active_notes
[note
->note()]) {
1369 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1370 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1371 old_rect
->property_x2() = x
;
1372 old_rect
->property_outline_what() = (guint32
) 0xF;
1374 _active_notes
[note
->note()] = ev
;
1376 /* outline all but right edge */
1377 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1379 /* outline all edges */
1380 ev
->property_outline_what() = (guint32
) 0xF;
1385 MidiRegionView::update_hit (CanvasHit
* ev
)
1387 boost::shared_ptr
<NoteType
> note
= ev
->note();
1389 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1390 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1391 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1392 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1397 /** Add a MIDI note to the view (with length).
1399 * If in sustained mode, notes with length 0 will be considered active
1400 * notes, and resolve_note should be called when the corresponding note off
1401 * event arrives, to properly display the note.
1404 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1406 CanvasNoteEvent
* event
= 0;
1408 assert(note
->time() >= 0);
1409 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1411 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1413 if (midi_view()->note_mode() == Sustained
) {
1415 CanvasNote
* ev_rect
= new CanvasNote(*this, *group
, note
);
1417 update_note (ev_rect
);
1421 MidiGhostRegion
* gr
;
1423 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1424 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1425 gr
->add_note(ev_rect
);
1429 } else if (midi_view()->note_mode() == Percussive
) {
1431 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1433 CanvasHit
* ev_diamond
= new CanvasHit(*this, *group
, diamond_size
, note
);
1435 update_hit (ev_diamond
);
1444 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1445 note_selected(event
, true);
1448 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1449 event
->show_velocity();
1451 event
->on_channel_selection_change(_last_channel_selection
);
1452 _events
.push_back(event
);
1463 MidiRegionView::step_add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1464 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1466 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1468 /* potentially extend region to hold new note */
1470 nframes64_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1471 nframes64_t region_end
= _region
->position() + _region
->length() - 1;
1473 if (end_frame
> region_end
) {
1474 _region
->set_length (end_frame
- _region
->position(), this);
1477 start_diff_command (_("step add"));
1478 diff_add_note (new_note
, true, false);
1481 // last_step_edit_note = new_note;
1485 MidiRegionView::add_pgm_change(PCEvent
& program
, const string
& displaytext
)
1487 assert(program
.time
>= 0);
1489 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1490 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(program
.time
));
1492 double height
= midi_stream_view()->contents_height();
1494 boost::shared_ptr
<CanvasProgramChange
> pgm_change
= boost::shared_ptr
<CanvasProgramChange
>(
1495 new CanvasProgramChange(*this, *group
,
1500 _custom_device_mode
,
1501 program
.time
, program
.channel
, program
.value
));
1503 // Show unless program change is beyond the region bounds
1504 if (program
.time
- _region
->start() >= _region
->length() || program
.time
< _region
->start()) {
1510 _pgm_changes
.push_back(pgm_change
);
1514 MidiRegionView::get_patch_key_at(double time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1516 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, channel
, MIDI_CTL_MSB_BANK
);
1517 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1519 if (msb_control
!= 0) {
1520 msb
= int(msb_control
->get_double(true, time
));
1523 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, channel
, MIDI_CTL_LSB_BANK
);
1524 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1526 if (lsb_control
!= 0) {
1527 lsb
= lsb_control
->get_double(true, time
);
1530 Evoral::Parameter
program_change(MidiPgmChangeAutomation
, channel
, 0);
1531 boost::shared_ptr
<Evoral::Control
> program_control
= _model
->control(program_change
);
1532 double program_number
= -1.0;
1533 if (program_control
!= 0) {
1534 program_number
= program_control
->get_double(true, time
);
1537 key
.msb
= (int) floor(msb
+ 0.5);
1538 key
.lsb
= (int) floor(lsb
+ 0.5);
1539 key
.program_number
= (int) floor(program_number
+ 0.5);
1540 assert(key
.is_sane());
1545 MidiRegionView::alter_program_change(PCEvent
& old_program
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1547 // TODO: Get the real event here and alter them at the original times
1548 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, old_program
.channel
, MIDI_CTL_MSB_BANK
);
1549 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1550 if (msb_control
!= 0) {
1551 msb_control
->set_double(double(new_patch
.msb
), true, old_program
.time
);
1554 // TODO: Get the real event here and alter them at the original times
1555 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, old_program
.channel
, MIDI_CTL_LSB_BANK
);
1556 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1557 if (lsb_control
!= 0) {
1558 lsb_control
->set_double(double(new_patch
.lsb
), true, old_program
.time
);
1561 Evoral::Parameter
program_change(MidiPgmChangeAutomation
, old_program
.channel
, 0);
1562 boost::shared_ptr
<Evoral::Control
> program_control
= _model
->control(program_change
);
1564 assert(program_control
!= 0);
1565 program_control
->set_double(float(new_patch
.program_number
), true, old_program
.time
);
1567 _pgm_changes
.clear ();
1568 display_program_changes (); // XXX would be nice to limit to just old_program.channel
1572 MidiRegionView::program_selected(CanvasProgramChange
& program
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1574 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1575 alter_program_change(program_change_event
, new_patch
);
1579 MidiRegionView::previous_program(CanvasProgramChange
& program
)
1581 if (program
.program() < 127) {
1582 MIDI::Name::PatchPrimaryKey key
;
1583 get_patch_key_at(program
.event_time(), program
.channel(), key
);
1584 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1586 key
.program_number
++;
1587 alter_program_change(program_change_event
, key
);
1592 MidiRegionView::next_program(CanvasProgramChange
& program
)
1594 if (program
.program() > 0) {
1595 MIDI::Name::PatchPrimaryKey key
;
1596 get_patch_key_at(program
.event_time(), program
.channel(), key
);
1597 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1599 key
.program_number
--;
1600 alter_program_change(program_change_event
, key
);
1605 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1607 if (_selection
.empty()) {
1611 if (_selection
.erase (cne
) > 0) {
1612 cerr
<< "Erased a CNE from selection\n";
1617 MidiRegionView::delete_selection()
1619 if (_selection
.empty()) {
1623 start_diff_command (_("delete selection"));
1625 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1626 if ((*i
)->selected()) {
1627 _diff_command
->remove((*i
)->note());
1637 MidiRegionView::delete_note (boost::shared_ptr
<NoteType
> n
)
1639 start_diff_command (_("delete note"));
1640 _diff_command
->remove (n
);
1643 trackview
.editor().hide_verbose_canvas_cursor ();
1647 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1649 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1650 if ((*i
)->selected() && (*i
) != ev
) {
1651 (*i
)->set_selected(false);
1652 (*i
)->hide_velocity();
1660 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1662 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1665 Selection::iterator tmp
= i
;
1668 (*i
)->set_selected (false);
1669 _selection
.erase (i
);
1678 /* don't bother with removing this regionview from the editor selection,
1679 since we're about to add another note, and thus put/keep this
1680 regionview in the editor selection.
1683 if (!ev
->selected()) {
1684 add_to_selection (ev
);
1689 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1691 uint8_t low_note
= 127;
1692 uint8_t high_note
= 0;
1693 MidiModel::Notes
& notes (_model
->notes());
1694 _optimization_iterator
= _events
.begin();
1700 if (extend
&& _selection
.empty()) {
1706 /* scan existing selection to get note range */
1708 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1709 if ((*i
)->note()->note() < low_note
) {
1710 low_note
= (*i
)->note()->note();
1712 if ((*i
)->note()->note() > high_note
) {
1713 high_note
= (*i
)->note()->note();
1717 low_note
= min (low_note
, notenum
);
1718 high_note
= max (high_note
, notenum
);
1721 no_sound_notes
= true;
1723 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1725 boost::shared_ptr
<NoteType
> note (*n
);
1726 CanvasNoteEvent
* cne
;
1727 bool select
= false;
1729 if (((1 << note
->channel()) & channel_mask
) != 0) {
1731 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1734 } else if (note
->note() == notenum
) {
1740 if ((cne
= find_canvas_note (note
)) != 0) {
1741 // extend is false because we've taken care of it,
1742 // since it extends by time range, not pitch.
1743 note_selected (cne
, add
, false);
1747 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1751 no_sound_notes
= false;
1755 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
1757 MidiModel::Notes
& notes (_model
->notes());
1758 _optimization_iterator
= _events
.begin();
1760 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1762 boost::shared_ptr
<NoteType
> note (*n
);
1763 CanvasNoteEvent
* cne
;
1765 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
1766 if ((cne
= find_canvas_note (note
)) != 0) {
1767 if (cne
->selected()) {
1768 note_deselected (cne
);
1770 note_selected (cne
, true, false);
1778 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
1781 clear_selection_except(ev
);
1786 if (!ev
->selected()) {
1787 add_to_selection (ev
);
1791 /* find end of latest note selected, select all between that and the start of "ev" */
1793 Evoral::MusicalTime earliest
= DBL_MAX
;
1794 Evoral::MusicalTime latest
= 0;
1796 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1797 if ((*i
)->note()->end_time() > latest
) {
1798 latest
= (*i
)->note()->end_time();
1800 if ((*i
)->note()->time() < earliest
) {
1801 earliest
= (*i
)->note()->time();
1805 if (ev
->note()->end_time() > latest
) {
1806 latest
= ev
->note()->end_time();
1809 if (ev
->note()->time() < earliest
) {
1810 earliest
= ev
->note()->time();
1813 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1815 /* find notes entirely within OR spanning the earliest..latest range */
1817 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
1818 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
1819 add_to_selection (*i
);
1823 /* if events were guaranteed to be time sorted, we could do this.
1824 but as of sept 10th 2009, they no longer are.
1827 if ((*i
)->note()->time() > latest
) {
1836 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
1838 remove_from_selection (ev
);
1842 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
1852 // TODO: Make this faster by storing the last updated selection rect, and only
1853 // adjusting things that are in the area that appears/disappeared.
1854 // We probably need a tree to be able to find events in O(log(n)) time.
1856 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1858 /* check if any corner of the note is inside the rect
1861 1) this is computing "touched by", not "contained by" the rect.
1862 2) this does not require that events be sorted in time.
1865 const double ix1
= (*i
)->x1();
1866 const double ix2
= (*i
)->x2();
1867 const double iy1
= (*i
)->y1();
1868 const double iy2
= (*i
)->y2();
1870 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
1871 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
1872 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
1873 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
1876 if (!(*i
)->selected()) {
1877 add_to_selection (*i
);
1879 } else if ((*i
)->selected()) {
1880 // Not inside rectangle
1881 remove_from_selection (*i
);
1887 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
1889 Selection::iterator i
= _selection
.find (ev
);
1891 if (i
!= _selection
.end()) {
1892 _selection
.erase (i
);
1895 ev
->set_selected (false);
1896 ev
->hide_velocity ();
1898 if (_selection
.empty()) {
1899 PublicEditor
& editor (trackview
.editor());
1900 editor
.get_selection().remove (this);
1905 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
1907 bool add_mrv_selection
= false;
1909 if (_selection
.empty()) {
1910 add_mrv_selection
= true;
1913 if (_selection
.insert (ev
).second
) {
1914 ev
->set_selected (true);
1915 play_midi_note ((ev
)->note());
1918 if (add_mrv_selection
) {
1919 PublicEditor
& editor (trackview
.editor());
1920 editor
.get_selection().add (this);
1925 MidiRegionView::move_selection(double dx
, double dy
)
1927 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1928 (*i
)->move_event(dx
, dy
);
1933 MidiRegionView::note_dropped(CanvasNoteEvent
*, double dt
, int8_t dnote
)
1935 assert (!_selection
.empty());
1937 uint8_t lowest_note_in_selection
= 127;
1938 uint8_t highest_note_in_selection
= 0;
1939 uint8_t highest_note_difference
= 0;
1941 // find highest and lowest notes first
1943 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1944 uint8_t pitch
= (*i
)->note()->note();
1945 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
1946 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
1950 cerr << "dnote: " << (int) dnote << endl;
1951 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1952 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1953 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1954 << int(highest_note_in_selection) << endl;
1955 cerr << "selection size: " << _selection.size() << endl;
1956 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1959 // Make sure the note pitch does not exceed the MIDI standard range
1960 if (highest_note_in_selection
+ dnote
> 127) {
1961 highest_note_difference
= highest_note_in_selection
- 127;
1964 start_diff_command(_("move notes"));
1966 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
1968 nframes64_t start_frames
= beats_to_frames((*i
)->note()->time());
1971 start_frames
+= snap_frame_to_frame(trackview
.editor().pixel_to_frame(dt
));
1973 start_frames
-= snap_frame_to_frame(trackview
.editor().pixel_to_frame(-dt
));
1976 Evoral::MusicalTime new_time
= frames_to_beats(start_frames
);
1982 diff_add_change (*i
, MidiModel::DiffCommand::StartTime
, new_time
);
1984 uint8_t original_pitch
= (*i
)->note()->note();
1985 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
1987 // keep notes in standard midi range
1988 clamp_to_0_127(new_pitch
);
1990 // keep original pitch if note is dragged outside valid midi range
1991 if ((original_pitch
!= 0 && new_pitch
== 0)
1992 || (original_pitch
!= 127 && new_pitch
== 127)) {
1993 new_pitch
= original_pitch
;
1996 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
1997 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
1999 diff_add_change (*i
, MidiModel::DiffCommand::NoteNumber
, new_pitch
);
2004 // care about notes being moved beyond the upper/lower bounds on the canvas
2005 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
2006 highest_note_in_selection
> midi_stream_view()->highest_note()) {
2007 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
2012 MidiRegionView::snap_pixel_to_frame(double x
)
2014 PublicEditor
& editor
= trackview
.editor();
2015 // x is region relative, convert it to global absolute frames
2016 nframes64_t frame
= editor
.pixel_to_frame(x
) + _region
->position();
2017 editor
.snap_to(frame
);
2018 return frame
- _region
->position(); // convert back to region relative
2022 MidiRegionView::snap_frame_to_frame(nframes64_t x
)
2024 PublicEditor
& editor
= trackview
.editor();
2025 // x is region relative, convert it to global absolute frames
2026 nframes64_t frame
= x
+ _region
->position();
2027 editor
.snap_to(frame
);
2028 return frame
- _region
->position(); // convert back to region relative
2032 MidiRegionView::snap_to_pixel(double x
)
2034 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
2038 MidiRegionView::get_position_pixels()
2040 nframes64_t region_frame
= get_position();
2041 return trackview
.editor().frame_to_pixel(region_frame
);
2045 MidiRegionView::get_end_position_pixels()
2047 nframes64_t frame
= get_position() + get_duration ();
2048 return trackview
.editor().frame_to_pixel(frame
);
2052 MidiRegionView::beats_to_frames(double beats
) const
2054 return _time_converter
.to(beats
);
2058 MidiRegionView::frames_to_beats(nframes64_t frames
) const
2060 return _time_converter
.from(frames
);
2064 MidiRegionView::begin_resizing (bool /*at_front*/)
2066 _resize_data
.clear();
2068 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2069 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
2071 // only insert CanvasNotes into the map
2073 NoteResizeData
*resize_data
= new NoteResizeData();
2074 resize_data
->canvas_note
= note
;
2076 // create a new SimpleRect from the note which will be the resize preview
2077 SimpleRect
*resize_rect
= new SimpleRect(
2078 *group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
2080 // calculate the colors: get the color settings
2081 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
2082 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
2085 // make the resize preview notes more transparent and bright
2086 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
2088 // calculate color based on note velocity
2089 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
2090 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity(), note
->selected()),
2094 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2095 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
2097 resize_data
->resize_rect
= resize_rect
;
2098 _resize_data
.push_back(resize_data
);
2103 /** Update resizing notes while user drags.
2104 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2105 * @param at_front which end of the note (true == note on, false == note off)
2106 * @param delta_x change in mouse position since the start of the drag
2107 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2108 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2109 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2110 * as the \a primary note.
2113 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote
* primary
, bool at_front
, double delta_x
, bool relative
)
2115 bool cursor_set
= false;
2117 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2118 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2119 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2124 current_x
= canvas_note
->x1() + delta_x
;
2126 current_x
= primary
->x1() + delta_x
;
2130 current_x
= canvas_note
->x2() + delta_x
;
2132 current_x
= primary
->x2() + delta_x
;
2137 resize_rect
->property_x1() = snap_to_pixel(current_x
);
2138 resize_rect
->property_x2() = canvas_note
->x2();
2140 resize_rect
->property_x2() = snap_to_pixel(current_x
);
2141 resize_rect
->property_x1() = canvas_note
->x1();
2147 beats
= snap_pixel_to_frame (current_x
);
2148 beats
= frames_to_beats (beats
);
2153 if (beats
< canvas_note
->note()->end_time()) {
2154 len
= canvas_note
->note()->time() - beats
;
2155 len
+= canvas_note
->note()->length();
2160 if (beats
>= canvas_note
->note()->end_time()) {
2161 len
= beats
- canvas_note
->note()->time();
2168 snprintf (buf
, sizeof (buf
), "%.3g beats", len
);
2169 trackview
.editor().show_verbose_canvas_cursor_with (buf
);
2178 /** Finish resizing notes when the user releases the mouse button.
2179 * Parameters the same as for \a update_resizing().
2182 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote
* primary
, bool at_front
, double delta_x
, bool relative
)
2184 start_diff_command(_("resize notes"));
2186 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2187 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2188 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2193 current_x
= canvas_note
->x1() + delta_x
;
2195 current_x
= primary
->x1() + delta_x
;
2199 current_x
= canvas_note
->x2() + delta_x
;
2201 current_x
= primary
->x2() + delta_x
;
2205 current_x
= snap_pixel_to_frame (current_x
);
2206 current_x
= frames_to_beats (current_x
);
2208 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2209 diff_add_change (canvas_note
, MidiModel::DiffCommand::StartTime
, current_x
);
2211 double len
= canvas_note
->note()->time() - current_x
;
2212 len
+= canvas_note
->note()->length();
2215 /* XXX convert to beats */
2216 diff_add_change (canvas_note
, MidiModel::DiffCommand::Length
, len
);
2221 double len
= current_x
- canvas_note
->note()->time();
2224 /* XXX convert to beats */
2225 diff_add_change (canvas_note
, MidiModel::DiffCommand::Length
, len
);
2233 _resize_data
.clear();
2238 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2240 uint8_t new_velocity
;
2243 new_velocity
= event
->note()->velocity() + velocity
;
2244 clamp_to_0_127(new_velocity
);
2246 new_velocity
= velocity
;
2249 event
->set_selected (event
->selected()); // change color
2251 diff_add_change (event
, MidiModel::DiffCommand::Velocity
, new_velocity
);
2255 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2260 new_note
= event
->note()->note() + note
;
2265 clamp_to_0_127 (new_note
);
2266 diff_add_change (event
, MidiModel::DiffCommand::NoteNumber
, new_note
);
2270 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2272 bool change_start
= false;
2273 bool change_length
= false;
2274 Evoral::MusicalTime new_start
= 0;
2275 Evoral::MusicalTime new_length
= 0;
2277 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2279 front_delta: if positive - move the start of the note later in time (shortening it)
2280 if negative - move the start of the note earlier in time (lengthening it)
2282 end_delta: if positive - move the end of the note later in time (lengthening it)
2283 if negative - move the end of the note earlier in time (shortening it)
2287 if (front_delta
< 0) {
2289 if (event
->note()->time() < -front_delta
) {
2292 new_start
= event
->note()->time() + front_delta
; // moves earlier
2295 /* start moved toward zero, so move the end point out to where it used to be.
2296 Note that front_delta is negative, so this increases the length.
2299 new_length
= event
->note()->length() - front_delta
;
2300 change_start
= true;
2301 change_length
= true;
2305 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2307 if (new_pos
< event
->note()->end_time()) {
2308 new_start
= event
->note()->time() + front_delta
;
2309 /* start moved toward the end, so move the end point back to where it used to be */
2310 new_length
= event
->note()->length() - front_delta
;
2311 change_start
= true;
2312 change_length
= true;
2319 bool can_change
= true;
2320 if (end_delta
< 0) {
2321 if (event
->note()->length() < -end_delta
) {
2327 new_length
= event
->note()->length() + end_delta
;
2328 change_length
= true;
2333 diff_add_change (event
, MidiModel::DiffCommand::StartTime
, new_start
);
2336 if (change_length
) {
2337 diff_add_change (event
, MidiModel::DiffCommand::Length
, new_length
);
2342 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2344 Evoral::MusicalTime new_time
;
2348 if (event
->note()->time() < -delta
) {
2351 new_time
= event
->note()->time() + delta
;
2354 new_time
= event
->note()->time() + delta
;
2360 diff_add_change (event
, MidiModel::DiffCommand::StartTime
, new_time
);
2364 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2368 if (_selection
.empty()) {
2383 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2384 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2390 start_diff_command(_("change velocities"));
2392 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2393 Selection::iterator next
= i
;
2395 change_note_velocity (*i
, delta
, true);
2401 if (!_selection
.empty()) {
2403 snprintf (buf
, sizeof (buf
), "Vel %d",
2404 (int) (*_selection
.begin())->note()->velocity());
2405 trackview
.editor().show_verbose_canvas_cursor_with (buf
);
2411 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2413 if (_selection
.empty()) {
2430 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2432 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2436 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2443 start_diff_command (_("transpose"));
2445 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2446 Selection::iterator next
= i
;
2448 change_note_note (*i
, delta
, true);
2456 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, bool start
, bool end
)
2458 Evoral::MusicalTime delta
;
2463 /* grab the current grid distance */
2465 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2467 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2468 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2477 start_diff_command (_("change note lengths"));
2479 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2480 Selection::iterator next
= i
;
2483 /* note the negation of the delta for start */
2485 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2494 MidiRegionView::nudge_notes (bool forward
)
2496 if (_selection
.empty()) {
2500 /* pick a note as the point along the timeline to get the nudge distance.
2501 its not necessarily the earliest note, so we may want to pull the notes out
2502 into a vector and sort before using the first one.
2505 nframes64_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2507 nframes64_t distance
;
2509 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2511 /* grid is off - use nudge distance */
2513 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2519 nframes64_t next_pos
= ref_point
;
2522 /* XXX need check on max_frames, but that needs max_frames64 or something */
2525 if (next_pos
== 0) {
2531 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2532 distance
= ref_point
- next_pos
;
2535 if (distance
== 0) {
2539 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2545 start_diff_command (_("nudge"));
2547 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2548 Selection::iterator next
= i
;
2550 change_note_time (*i
, delta
, true);
2558 MidiRegionView::change_channel(uint8_t channel
)
2560 start_diff_command(_("change channel"));
2561 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2562 diff_add_change (*i
, MidiModel::DiffCommand::Channel
, channel
);
2570 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2572 if (_mouse_state
== SelectTouchDragging
) {
2573 note_selected (ev
, true);
2576 show_verbose_canvas_cursor (ev
->note ());
2580 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
* note
)
2582 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2583 (*i
)->hide_velocity ();
2586 trackview
.editor().hide_verbose_canvas_cursor ();
2590 MidiRegionView::switch_source(boost::shared_ptr
<Source
> src
)
2592 boost::shared_ptr
<MidiSource
> msrc
= boost::dynamic_pointer_cast
<MidiSource
>(src
);
2594 display_model(msrc
->model());
2598 MidiRegionView::set_frame_color()
2601 if (_selected
&& should_show_selection
) {
2602 frame
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2604 frame
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2610 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2614 case FilterChannels
:
2615 _force_channel
= -1;
2618 _force_channel
= mask
;
2619 mask
= 0xFFFF; // Show all notes as active (below)
2622 // Update notes for selection
2623 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2624 (*i
)->on_channel_selection_change(mask
);
2627 _last_channel_selection
= mask
;
2631 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2633 _model_name
= model
;
2634 _custom_device_mode
= custom_device_mode
;
2639 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
2641 if (_selection
.empty()) {
2645 PublicEditor
& editor (trackview
.editor());
2650 editor
.get_cut_buffer().add (selection_as_cut_buffer());
2658 start_diff_command();
2660 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2666 diff_remove_note (*i
);
2676 MidiRegionView::selection_as_cut_buffer () const
2680 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2681 NoteType
* n
= (*i
)->note().get();
2682 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
2685 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
2692 MidiRegionView::paste (nframes64_t pos
, float times
, const MidiCutBuffer
& mcb
)
2698 start_diff_command (_("paste"));
2700 Evoral::MusicalTime beat_delta
;
2701 Evoral::MusicalTime paste_pos_beats
;
2702 Evoral::MusicalTime duration
;
2703 Evoral::MusicalTime end_point
= 0;
2705 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
2706 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
2707 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
2708 paste_pos_beats
= 0;
2710 _selection
.clear ();
2712 for (int n
= 0; n
< (int) times
; ++n
) {
2714 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
2716 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
2717 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
2719 /* make all newly added notes selected */
2721 diff_add_note (copied_note
, true);
2722 end_point
= copied_note
->end_time();
2725 paste_pos_beats
+= duration
;
2728 /* if we pasted past the current end of the region, extend the region */
2730 nframes64_t end_frame
= _region
->position() + beats_to_frames (end_point
);
2731 nframes64_t region_end
= _region
->position() + _region
->length() - 1;
2733 if (end_frame
> region_end
) {
2735 trackview
.session()->begin_reversible_command (_("paste"));
2737 _region
->clear_history ();
2738 _region
->set_length (end_frame
, this);
2739 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
2745 struct EventNoteTimeEarlyFirstComparator
{
2746 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
2747 return a
->note()->time() < b
->note()->time();
2752 MidiRegionView::time_sort_events ()
2754 if (!_sort_needed
) {
2758 EventNoteTimeEarlyFirstComparator cmp
;
2761 _sort_needed
= false;
2765 MidiRegionView::goto_next_note ()
2767 // nframes64_t pos = -1;
2768 bool use_next
= false;
2770 if (_events
.back()->selected()) {
2774 time_sort_events ();
2776 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2777 if ((*i
)->selected()) {
2780 } else if (use_next
) {
2782 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2787 /* use the first one */
2789 unique_select (_events
.front());
2794 MidiRegionView::goto_previous_note ()
2796 // nframes64_t pos = -1;
2797 bool use_next
= false;
2799 if (_events
.front()->selected()) {
2803 time_sort_events ();
2805 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
2806 if ((*i
)->selected()) {
2809 } else if (use_next
) {
2811 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2816 /* use the last one */
2818 unique_select (*(_events
.rbegin()));
2822 MidiRegionView::selection_as_notelist (Notes
& selected
, bool allow_all_if_none_selected
)
2824 bool had_selected
= false;
2826 time_sort_events ();
2828 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2829 if ((*i
)->selected()) {
2830 selected
.insert ((*i
)->note());
2831 had_selected
= true;
2835 if (allow_all_if_none_selected
&& !had_selected
) {
2836 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2837 selected
.insert ((*i
)->note());
2843 MidiRegionView::update_ghost_note (double x
, double y
)
2849 nframes64_t f
= trackview
.editor().pixel_to_frame (x
) + _region
->position ();
2850 trackview
.editor().snap_to (f
);
2851 f
-= _region
->position ();
2854 Evoral::MusicalTime beats
= trackview
.editor().get_grid_type_as_beats (success
, f
);
2859 double length
= frames_to_beats (snap_frame_to_frame (f
+ beats_to_frames (beats
)) - f
);
2861 _ghost_note
->note()->set_time (frames_to_beats (f
+ _region
->start()));
2862 _ghost_note
->note()->set_length (length
);
2863 _ghost_note
->note()->set_note (midi_stream_view()->y_to_note (y
));
2865 update_note (_ghost_note
);
2867 show_verbose_canvas_cursor (_ghost_note
->note ());
2871 MidiRegionView::create_ghost_note (double x
, double y
)
2876 boost::shared_ptr
<NoteType
> g (new NoteType
);
2877 _ghost_note
= new NoEventCanvasNote (*this, *group
, g
);
2878 update_ghost_note (x
, y
);
2879 _ghost_note
->show ();
2884 show_verbose_canvas_cursor (_ghost_note
->note ());
2888 MidiRegionView::snap_changed ()
2894 create_ghost_note (_last_ghost_x
, _last_ghost_y
);
2898 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr
<NoteType
> n
) const
2901 snprintf (buf
, sizeof (buf
), "%s (%d)\nVel %d",
2902 Evoral::midi_note_name (n
->note()).c_str(),
2904 (int) n
->velocity());
2905 trackview
.editor().show_verbose_canvas_cursor_with (buf
);
2909 MidiRegionView::drop_down_keys ()
2911 _mouse_state
= None
;
2915 MidiRegionView::maybe_select_by_position (GdkEventButton
* ev
, double x
, double y
)
2917 double note
= midi_stream_view()->y_to_note(y
);
2919 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
2921 cerr
<< "Selecting by position\n";
2923 uint16_t chn_mask
= mtv
->channel_selector().get_selected_channels();
2925 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
2926 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchGreaterThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
2927 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
2928 get_events (e
, Evoral::Sequence
<Evoral::MusicalTime
>::PitchLessThanOrEqual
, (uint8_t) floor (note
), chn_mask
);
2933 bool add_mrv_selection
= false;
2935 if (_selection
.empty()) {
2936 add_mrv_selection
= true;
2939 for (Events::iterator i
= e
.begin(); i
!= e
.end(); ++i
) {
2940 if (_selection
.insert (*i
).second
) {
2941 (*i
)->set_selected (true);
2945 if (add_mrv_selection
) {
2946 PublicEditor
& editor (trackview
.editor());
2947 editor
.get_selection().add (this);
2952 MidiRegionView::color_handler ()
2954 RegionView::color_handler ();
2956 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2957 (*i
)->set_selected ((*i
)->selected()); // will change color
2960 /* XXX probably more to do here */
2964 MidiRegionView::enable_display (bool yn
)
2966 RegionView::enable_display (yn
);