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/Control.hpp"
45 #include "automation_region_view.h"
46 #include "automation_time_axis.h"
47 #include "canvas-hit.h"
48 #include "canvas-note.h"
49 #include "canvas-program-change.h"
50 #include "ghostregion.h"
51 #include "gui_thread.h"
53 #include "midi_cut_buffer.h"
54 #include "midi_list_editor.h"
55 #include "midi_region_view.h"
56 #include "midi_streamview.h"
57 #include "midi_time_axis.h"
58 #include "midi_time_axis.h"
59 #include "midi_util.h"
60 #include "public_editor.h"
61 #include "selection.h"
62 #include "simpleline.h"
63 #include "streamview.h"
68 using namespace ARDOUR
;
70 using namespace Editing
;
71 using namespace ArdourCanvas
;
72 using Gtkmm2ext::Keyboard
;
74 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
75 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
const & basic_color
)
76 : RegionView (parent
, tv
, r
, spu
, basic_color
)
78 , _last_channel_selection(0xFFFF)
79 , _default_note_length(1.0)
80 , _current_range_min(0)
81 , _current_range_max(0)
82 , _model_name(string())
83 , _custom_device_mode(string())
85 , _note_group(new ArdourCanvas::Group(*parent
))
91 , _optimization_iterator (_events
.end())
93 , no_sound_notes (false)
95 _note_group
->raise_to_top();
98 MidiRegionView::MidiRegionView (ArdourCanvas::Group
*parent
, RouteTimeAxisView
&tv
,
99 boost::shared_ptr
<MidiRegion
> r
, double spu
, Gdk::Color
& basic_color
,
100 TimeAxisViewItem::Visibility visibility
)
101 : RegionView (parent
, tv
, r
, spu
, basic_color
, false, visibility
)
103 , _last_channel_selection(0xFFFF)
104 , _default_note_length(1.0)
105 , _model_name(string())
106 , _custom_device_mode(string())
108 , _note_group(new ArdourCanvas::Group(*parent
))
113 , _sort_needed (true)
114 , _optimization_iterator (_events
.end())
116 , no_sound_notes (false)
119 _note_group
->raise_to_top();
123 MidiRegionView::MidiRegionView (const MidiRegionView
& other
)
124 : sigc::trackable(other
)
127 , _last_channel_selection(0xFFFF)
128 , _default_note_length(1.0)
129 , _model_name(string())
130 , _custom_device_mode(string())
132 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
137 , _sort_needed (true)
138 , _optimization_iterator (_events
.end())
140 , no_sound_notes (false)
145 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
146 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
151 MidiRegionView::MidiRegionView (const MidiRegionView
& other
, boost::shared_ptr
<MidiRegion
> region
)
152 : RegionView (other
, boost::shared_ptr
<Region
> (region
))
154 , _last_channel_selection(0xFFFF)
155 , _default_note_length(1.0)
156 , _model_name(string())
157 , _custom_device_mode(string())
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
164 , _sort_needed (true)
165 , _optimization_iterator (_events
.end())
167 , no_sound_notes (false)
172 UINT_TO_RGBA (other
.fill_color
, &r
, &g
, &b
, &a
);
173 c
.set_rgb_p (r
/255.0, g
/255.0, b
/255.0);
179 MidiRegionView::init (Gdk::Color
const & basic_color
, bool wfd
)
181 CanvasNoteEvent::CanvasNoteEventDeleted
.connect (note_delete_connection
, MISSING_INVALIDATOR
,
182 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection
, this, _1
),
186 midi_region()->midi_source(0)->load_model();
189 _model
= midi_region()->midi_source(0)->model();
190 _enable_display
= false;
192 RegionView::init (basic_color
, false);
194 compute_colors (basic_color
);
196 set_height (trackview
.current_height());
199 region_sync_changed ();
200 region_resized (ARDOUR::bounds_change
);
203 reset_width_dependent_items (_pixel_width
);
207 _enable_display
= true;
210 display_model (_model
);
214 group
->raise_to_top();
215 group
->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event
), false);
217 midi_view()->signal_channel_mode_changed().connect(
218 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed
));
220 midi_view()->signal_midi_patch_settings_changed().connect(
221 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed
));
225 MidiRegionView::canvas_event(GdkEvent
* ev
)
227 PublicEditor
& editor (trackview
.editor());
229 if (!editor
.internal_editing()) {
233 static double drag_start_x
, drag_start_y
;
234 static double last_x
, last_y
;
235 double event_x
, event_y
;
236 nframes64_t event_frame
= 0;
239 static ArdourCanvas::SimpleRect
* drag_rect
= 0;
241 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
242 to its items, which means that ev->type == GDK_SCROLL will never be seen
247 fine
= Keyboard::modifier_state_equals (ev
->scroll
.state
, Keyboard::Level4Modifier
);
249 if (ev
->scroll
.direction
== GDK_SCROLL_UP
) {
250 change_velocities (true, fine
, false);
252 } else if (ev
->scroll
.direction
== GDK_SCROLL_DOWN
) {
253 change_velocities (false, fine
, false);
262 /* since GTK bindings are generally activated on press, and since
263 detectable auto-repeat is the name of the game and only sends
264 repeated presses, carry out key actions at key press, not release.
267 if (ev
->key
.keyval
== GDK_Alt_L
|| ev
->key
.keyval
== GDK_Alt_R
){
268 _mouse_state
= SelectTouchDragging
;
271 } else if (ev
->key
.keyval
== GDK_Escape
) {
275 } else if (ev
->key
.keyval
== GDK_comma
|| ev
->key
.keyval
== GDK_period
) {
277 bool start
= (ev
->key
.keyval
== GDK_comma
);
278 bool end
= (ev
->key
.keyval
== GDK_period
);
279 bool shorter
= Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::PrimaryModifier
);
280 fine
= Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::SecondaryModifier
);
282 change_note_lengths (fine
, shorter
, start
, end
);
286 } else if (ev
->key
.keyval
== GDK_Delete
) {
291 } else if (ev
->key
.keyval
== GDK_Tab
) {
293 if (Keyboard::modifier_state_equals (ev
->key
.state
, Keyboard::PrimaryModifier
)) {
294 goto_previous_note ();
300 } else if (ev
->key
.keyval
== GDK_Up
) {
302 bool allow_smush
= Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::SecondaryModifier
);
303 bool fine
= Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::TertiaryModifier
);
305 if (Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::PrimaryModifier
)) {
306 change_velocities (true, fine
, allow_smush
);
308 transpose (true, fine
, allow_smush
);
312 } else if (ev
->key
.keyval
== GDK_Down
) {
314 bool allow_smush
= Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::SecondaryModifier
);
315 fine
= Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::TertiaryModifier
);
317 if (Keyboard::modifier_state_contains (ev
->key
.state
, Keyboard::PrimaryModifier
)) {
318 change_velocities (false, fine
, allow_smush
);
320 transpose (false, fine
, allow_smush
);
324 } else if (ev
->key
.keyval
== GDK_Left
) {
329 } else if (ev
->key
.keyval
== GDK_Right
) {
334 } else if (ev
->key
.keyval
== GDK_Control_L
) {
337 } else if (ev
->key
.keyval
== GDK_r
) {
338 /* if we're not step editing, this really doesn't matter */
339 midi_view()->step_edit_rest ();
345 case GDK_KEY_RELEASE
:
346 if (ev
->key
.keyval
== GDK_Alt_L
|| ev
->key
.keyval
== GDK_Alt_R
) {
352 case GDK_BUTTON_PRESS
:
353 if (_mouse_state
!= SelectTouchDragging
&& ev
->button
.button
== 1) {
354 _pressed_button
= ev
->button
.button
;
355 _mouse_state
= Pressed
;
358 _pressed_button
= ev
->button
.button
;
361 case GDK_2BUTTON_PRESS
:
364 case GDK_ENTER_NOTIFY
:
365 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
366 Keyboard::magic_widget_grab_focus();
370 case GDK_MOTION_NOTIFY
:
371 event_x
= ev
->motion
.x
;
372 event_y
= ev
->motion
.y
;
373 group
->w2i(event_x
, event_y
);
375 // convert event_x to global frame
376 event_frame
= trackview
.editor().pixel_to_frame(event_x
) + _region
->position();
377 trackview
.editor().snap_to(event_frame
);
378 // convert event_frame back to local coordinates relative to position
379 event_frame
-= _region
->position();
381 switch (_mouse_state
) {
382 case Pressed
: // Drag start
385 if (_pressed_button
== 1 && editor
.current_mouse_mode() == MouseObject
) {
386 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
387 Gdk::Cursor(Gdk::FLEUR
), ev
->motion
.time
);
390 drag_start_x
= event_x
;
391 drag_start_y
= event_y
;
393 drag_rect
= new ArdourCanvas::SimpleRect(*group
);
394 drag_rect
->property_x1() = event_x
;
395 drag_rect
->property_y1() = event_y
;
396 drag_rect
->property_x2() = event_x
;
397 drag_rect
->property_y2() = event_y
;
398 drag_rect
->property_outline_what() = 0xFF;
399 drag_rect
->property_outline_color_rgba()
400 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline
.get();
401 drag_rect
->property_fill_color_rgba()
402 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill
.get();
404 _mouse_state
= SelectRectDragging
;
407 // Add note drag start
408 } else if (editor
.current_mouse_mode() == MouseRange
) {
409 group
->grab(GDK_POINTER_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
410 Gdk::Cursor(Gdk::FLEUR
), ev
->motion
.time
);
413 drag_start_x
= event_x
;
414 drag_start_y
= event_y
;
416 drag_rect
= new ArdourCanvas::SimpleRect(*group
);
417 drag_rect
->property_x1() = trackview
.editor().frame_to_pixel(event_frame
);
419 drag_rect
->property_y1() = midi_stream_view()->note_to_y(
420 midi_stream_view()->y_to_note(event_y
));
421 drag_rect
->property_x2() = event_x
;
422 drag_rect
->property_y2() = drag_rect
->property_y1()
423 + floor(midi_stream_view()->note_height());
424 drag_rect
->property_outline_what() = 0xFF;
425 drag_rect
->property_outline_color_rgba() = 0xFFFFFF99;
426 drag_rect
->property_fill_color_rgba() = 0xFFFFFF66;
428 _mouse_state
= AddDragging
;
434 case SelectRectDragging
: // Select drag motion
435 case AddDragging
: // Add note drag motion
436 if (ev
->motion
.is_hint
) {
439 GdkModifierType state
;
440 gdk_window_get_pointer(ev
->motion
.window
, &t_x
, &t_y
, &state
);
445 if (_mouse_state
== AddDragging
)
446 event_x
= trackview
.editor().frame_to_pixel(event_frame
);
449 if (event_x
> drag_start_x
)
450 drag_rect
->property_x2() = event_x
;
452 drag_rect
->property_x1() = event_x
;
455 if (drag_rect
&& _mouse_state
== SelectRectDragging
) {
456 if (event_y
> drag_start_y
)
457 drag_rect
->property_y2() = event_y
;
459 drag_rect
->property_y1() = event_y
;
461 update_drag_selection(drag_start_x
, event_x
, drag_start_y
, event_y
);
467 case SelectTouchDragging
:
475 case GDK_BUTTON_RELEASE
:
476 event_x
= ev
->motion
.x
;
477 event_y
= ev
->motion
.y
;
478 group
->w2i(event_x
, event_y
);
479 group
->ungrab(ev
->button
.time
);
480 event_frame
= trackview
.editor().pixel_to_frame(event_x
);
482 if (ev
->button
.button
== 3) {
484 } else if (_pressed_button
!= 1) {
488 switch (_mouse_state
) {
489 case Pressed
: // Clicked
490 switch (editor
.current_mouse_mode()) {
496 create_note_at(event_x
, event_y
, _default_note_length
);
503 case SelectRectDragging
: // Select drag done
508 case AddDragging
: // Add drag done
510 if (drag_rect
->property_x2() > drag_rect
->property_x1() + 2) {
511 const double x
= drag_rect
->property_x1();
512 const double length
= trackview
.editor().pixel_to_frame(
513 drag_rect
->property_x2() - drag_rect
->property_x1());
515 create_note_at(x
, drag_rect
->property_y1(), frames_to_beats(length
));
530 MidiRegionView::show_list_editor ()
533 _list_editor
= new MidiListEditor (trackview
.session(), midi_region());
535 _list_editor
->present ();
538 /** Add a note to the model, and the view, at a canvas (click) coordinate.
539 * \param x horizontal position in pixels
540 * \param y vertical position in pixels
541 * \param length duration of the note in beats */
543 MidiRegionView::create_note_at(double x
, double y
, double length
)
545 MidiTimeAxisView
* const mtv
= dynamic_cast<MidiTimeAxisView
*>(&trackview
);
546 MidiStreamView
* const view
= mtv
->midi_view();
548 double note
= midi_stream_view()->y_to_note(y
);
551 assert(note
<= 127.0);
553 // Start of note in frames relative to region start
554 nframes64_t start_frames
= snap_frame_to_frame(trackview
.editor().pixel_to_frame(x
));
555 assert(start_frames
>= 0);
558 length
= frames_to_beats(
559 snap_frame_to_frame(start_frames
+ beats_to_frames(length
)) - start_frames
);
561 const boost::shared_ptr
<NoteType
> new_note(new NoteType(0,
562 frames_to_beats(start_frames
+ _region
->start()), length
,
563 (uint8_t)note
, 0x40));
565 view
->update_note_range(new_note
->note());
567 MidiModel::DeltaCommand
* cmd
= _model
->new_delta_command("add note");
569 _model
->apply_command(*trackview
.session(), cmd
);
571 play_midi_note (new_note
);
575 MidiRegionView::clear_events()
580 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
581 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
586 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
591 _pgm_changes
.clear();
593 _optimization_iterator
= _events
.end();
598 MidiRegionView::display_model(boost::shared_ptr
<MidiModel
> model
)
601 content_connection
.disconnect ();
602 _model
->ContentsChanged
.connect (content_connection
, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model
, this), gui_context());
606 if (_enable_display
) {
613 MidiRegionView::start_delta_command(string name
)
615 if (!_delta_command
) {
616 _delta_command
= _model
->new_delta_command(name
);
621 MidiRegionView::start_diff_command(string name
)
623 if (!_diff_command
) {
624 _diff_command
= _model
->new_diff_command(name
);
629 MidiRegionView::delta_add_note(const boost::shared_ptr
<NoteType
> note
, bool selected
, bool show_velocity
)
631 if (_delta_command
) {
632 _delta_command
->add(note
);
635 _marked_for_selection
.insert(note
);
638 _marked_for_velocity
.insert(note
);
643 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent
* ev
)
645 if (_delta_command
&& ev
->note()) {
646 _delta_command
->remove(ev
->note());
651 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
652 MidiModel::DiffCommand::Property property
,
656 _diff_command
->change (ev
->note(), property
, val
);
661 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent
* ev
,
662 MidiModel::DiffCommand::Property property
,
663 Evoral::MusicalTime val
)
666 _diff_command
->change (ev
->note(), property
, val
);
671 MidiRegionView::apply_delta()
673 if (!_delta_command
) {
677 // Mark all selected notes for selection when model reloads
678 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
679 _marked_for_selection
.insert((*i
)->note());
682 _model
->apply_command(*trackview
.session(), _delta_command
);
684 midi_view()->midi_track()->playlist_modified();
686 _marked_for_selection
.clear();
687 _marked_for_velocity
.clear();
691 MidiRegionView::apply_diff ()
693 if (!_diff_command
) {
697 _model
->apply_command(*trackview
.session(), _diff_command
);
699 midi_view()->midi_track()->playlist_modified();
701 _marked_for_velocity
.clear();
705 MidiRegionView::apply_delta_as_subcommand()
707 if (!_delta_command
) {
711 // Mark all selected notes for selection when model reloads
712 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
713 _marked_for_selection
.insert((*i
)->note());
716 _model
->apply_command_as_subcommand(*trackview
.session(), _delta_command
);
718 midi_view()->midi_track()->playlist_modified();
720 _marked_for_selection
.clear();
721 _marked_for_velocity
.clear();
725 MidiRegionView::apply_diff_as_subcommand()
727 if (!_diff_command
) {
731 // Mark all selected notes for selection when model reloads
732 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
733 _marked_for_selection
.insert((*i
)->note());
736 _model
->apply_command_as_subcommand(*trackview
.session(), _diff_command
);
738 midi_view()->midi_track()->playlist_modified();
740 _marked_for_selection
.clear();
741 _marked_for_velocity
.clear();
745 MidiRegionView::abort_command()
747 delete _delta_command
;
749 delete _diff_command
;
755 MidiRegionView::find_canvas_note (boost::shared_ptr
<NoteType
> note
)
757 if (_optimization_iterator
!= _events
.end()) {
758 ++_optimization_iterator
;
761 if (_optimization_iterator
!= _events
.end() && (*_optimization_iterator
)->note() == note
) {
762 return *_optimization_iterator
;
765 for (_optimization_iterator
= _events
.begin(); _optimization_iterator
!= _events
.end(); ++_optimization_iterator
) {
766 if ((*_optimization_iterator
)->note() == note
) {
767 return *_optimization_iterator
;
775 MidiRegionView::redisplay_model()
777 // Don't redisplay the model if we're currently recording and displaying that
783 cerr
<< "MidiRegionView::redisplay_model called without a model" << endmsg
;
787 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
791 MidiModel::ReadLock
lock(_model
->read_lock());
793 MidiModel::Notes
& notes (_model
->notes());
794 _optimization_iterator
= _events
.begin();
796 cerr
<< "++++++++++ MIDI REdisplay\n";
798 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
800 boost::shared_ptr
<NoteType
> note (*n
);
801 CanvasNoteEvent
* cne
;
804 if (note_in_region_range (note
, visible
)) {
806 if ((cne
= find_canvas_note (note
)) != 0) {
813 if ((cn
= dynamic_cast<CanvasNote
*>(cne
)) != 0) {
815 } else if ((ch
= dynamic_cast<CanvasHit
*>(cne
)) != 0) {
827 add_note (note
, visible
);
832 if ((cne
= find_canvas_note (note
)) != 0) {
840 /* remove note items that are no longer valid */
842 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ) {
843 if (!(*i
)->valid ()) {
845 i
= _events
.erase (i
);
852 display_program_changes();
854 _marked_for_selection
.clear ();
855 _marked_for_velocity
.clear ();
857 /* we may have caused _events to contain things out of order (e.g. if a note
858 moved earlier or later). we don't generally need them in time order, but
859 make a note that a sort is required for those cases that require it.
866 MidiRegionView::display_program_changes()
868 boost::shared_ptr
<Evoral::Control
> control
= _model
->control(MidiPgmChangeAutomation
);
873 Glib::Mutex::Lock
lock (control
->list()->lock());
875 uint8_t channel
= control
->parameter().channel();
877 for (AutomationList::const_iterator event
= control
->list()->begin();
878 event
!= control
->list()->end(); ++event
) {
879 double event_time
= (*event
)->when
;
880 double program_number
= floor((*event
)->value
+ 0.5);
882 // Get current value of bank select MSB at time of the program change
883 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, channel
, MIDI_CTL_MSB_BANK
);
884 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
886 if (msb_control
!= 0) {
887 msb
= uint8_t(floor(msb_control
->get_float(true, event_time
) + 0.5));
890 // Get current value of bank select LSB at time of the program change
891 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, channel
, MIDI_CTL_LSB_BANK
);
892 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
894 if (lsb_control
!= 0) {
895 lsb
= uint8_t(floor(lsb_control
->get_float(true, event_time
) + 0.5));
898 MIDI::Name::PatchPrimaryKey
patch_key(msb
, lsb
, program_number
);
900 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
901 MIDI::Name::MidiPatchManager::instance().find_patch(
902 _model_name
, _custom_device_mode
, channel
, patch_key
);
904 PCEvent
program_change(event_time
, uint8_t(program_number
), channel
);
907 add_pgm_change(program_change
, patch
->name());
910 snprintf(buf
, 4, "%d", int(program_number
));
911 add_pgm_change(program_change
, buf
);
917 MidiRegionView::display_sysexes()
919 for (MidiModel::SysExes::const_iterator i
= _model
->sysexes().begin(); i
!= _model
->sysexes().end(); ++i
) {
920 Evoral::MusicalTime time
= (*i
)->time();
925 for (uint32_t b
= 0; b
< (*i
)->size(); ++b
) {
926 str
<< int((*i
)->buffer()[b
]);
927 if (b
!= (*i
)->size() -1) {
931 string text
= str
.str();
933 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
935 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(time
));
937 double height
= midi_stream_view()->contents_height();
939 boost::shared_ptr
<CanvasSysEx
> sysex
= boost::shared_ptr
<CanvasSysEx
>(
940 new CanvasSysEx(*this, *group
, text
, height
, x
, 1.0));
942 // Show unless program change is beyond the region bounds
943 if (time
- _region
->start() >= _region
->length() || time
< _region
->start()) {
949 _sys_exes
.push_back(sysex
);
954 MidiRegionView::~MidiRegionView ()
956 in_destructor
= true;
958 note_delete_connection
.disconnect ();
962 RegionViewGoingAway (this); /* EMIT_SIGNAL */
971 delete _delta_command
;
975 MidiRegionView::region_resized (const PropertyChange
& what_changed
)
977 RegionView::region_resized(what_changed
);
979 if (what_changed
.contains (ARDOUR::Properties::position
)) {
980 set_duration(_region
->length(), 0);
981 if (_enable_display
) {
988 MidiRegionView::reset_width_dependent_items (double pixel_width
)
990 RegionView::reset_width_dependent_items(pixel_width
);
991 assert(_pixel_width
== pixel_width
);
993 if (_enable_display
) {
999 MidiRegionView::set_height (double height
)
1001 static const double FUDGE
= 2.0;
1002 const double old_height
= _height
;
1003 RegionView::set_height(height
);
1004 _height
= height
- FUDGE
;
1006 apply_note_range(midi_stream_view()->lowest_note(),
1007 midi_stream_view()->highest_note(),
1008 height
!= old_height
+ FUDGE
);
1011 name_pixbuf
->raise_to_top();
1016 /** Apply the current note range from the stream view
1017 * by repositioning/hiding notes as necessary
1020 MidiRegionView::apply_note_range (uint8_t min
, uint8_t max
, bool force
)
1022 if (!_enable_display
) {
1026 if (!force
&& _current_range_min
== min
&& _current_range_max
== max
) {
1030 _current_range_min
= min
;
1031 _current_range_max
= max
;
1033 for (Events::const_iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1034 CanvasNoteEvent
* event
= *i
;
1035 boost::shared_ptr
<NoteType
> note (event
->note());
1037 if (note
->note() < _current_range_min
||
1038 note
->note() > _current_range_max
) {
1044 if (CanvasNote
* cnote
= dynamic_cast<CanvasNote
*>(event
)) {
1046 const double y1
= midi_stream_view()->note_to_y(note
->note());
1047 const double y2
= y1
+ floor(midi_stream_view()->note_height());
1049 cnote
->property_y1() = y1
;
1050 cnote
->property_y2() = y2
;
1052 } else if (CanvasHit
* chit
= dynamic_cast<CanvasHit
*>(event
)) {
1054 double x
= trackview
.editor().frame_to_pixel(
1055 beats_to_frames(note
->time()) - _region
->start());
1056 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1057 double y
= midi_stream_view()->note_to_y(event
->note()->note())
1058 + ((diamond_size
-2.0) / 4.0);
1060 chit
->set_height (diamond_size
);
1061 chit
->move (x
- chit
->x1(), y
- chit
->y1());
1068 MidiRegionView::add_ghost (TimeAxisView
& tv
)
1072 double unit_position
= _region
->position () / samples_per_unit
;
1073 MidiTimeAxisView
* mtv
= dynamic_cast<MidiTimeAxisView
*>(&tv
);
1074 MidiGhostRegion
* ghost
;
1076 if (mtv
&& mtv
->midi_view()) {
1077 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1078 to allow having midi notes on top of note lines and waveforms.
1080 ghost
= new MidiGhostRegion (*mtv
->midi_view(), trackview
, unit_position
);
1082 ghost
= new MidiGhostRegion (tv
, trackview
, unit_position
);
1085 ghost
->set_height ();
1086 ghost
->set_duration (_region
->length() / samples_per_unit
);
1087 ghosts
.push_back (ghost
);
1089 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1090 if ((note
= dynamic_cast<CanvasNote
*>(*i
)) != 0) {
1091 ghost
->add_note(note
);
1095 GhostRegion::CatchDeletion
.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost
, this, _1
), gui_context());
1101 /** Begin tracking note state for successive calls to add_event
1104 MidiRegionView::begin_write()
1106 assert(!_active_notes
);
1107 _active_notes
= new CanvasNote
*[128];
1108 for (unsigned i
=0; i
< 128; ++i
) {
1109 _active_notes
[i
] = 0;
1114 /** Destroy note state for add_event
1117 MidiRegionView::end_write()
1119 delete[] _active_notes
;
1121 _marked_for_selection
.clear();
1122 _marked_for_velocity
.clear();
1126 /** Resolve an active MIDI note (while recording).
1129 MidiRegionView::resolve_note(uint8_t note
, double end_time
)
1131 if (midi_view()->note_mode() != Sustained
) {
1135 if (_active_notes
&& _active_notes
[note
]) {
1136 const nframes64_t end_time_frames
= beats_to_frames(end_time
);
1137 _active_notes
[note
]->property_x2() = trackview
.editor().frame_to_pixel(end_time_frames
);
1138 _active_notes
[note
]->property_outline_what() = (guint32
) 0xF; // all edges
1139 _active_notes
[note
] = 0;
1144 /** Extend active notes to rightmost edge of region (if length is changed)
1147 MidiRegionView::extend_active_notes()
1149 if (!_active_notes
) {
1153 for (unsigned i
=0; i
< 128; ++i
) {
1154 if (_active_notes
[i
]) {
1155 _active_notes
[i
]->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1161 MidiRegionView::play_midi_note(boost::shared_ptr
<NoteType
> note
)
1163 if (no_sound_notes
|| !trackview
.editor().sound_notes()) {
1167 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1170 route_ui
->midi_track()->write_immediate_event(
1171 note
->on_event().size(), note
->on_event().buffer());
1173 const double note_length_beats
= (note
->off_event().time() - note
->on_event().time());
1174 nframes_t note_length_ms
= beats_to_frames(note_length_beats
)
1175 * (1000 / (double)route_ui
->session()->nominal_frame_rate());
1176 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off
), note
),
1177 note_length_ms
, G_PRIORITY_DEFAULT
);
1181 MidiRegionView::play_midi_note_off(boost::shared_ptr
<NoteType
> note
)
1183 RouteUI
* route_ui
= dynamic_cast<RouteUI
*> (&trackview
);
1186 route_ui
->midi_track()->write_immediate_event(
1187 note
->off_event().size(), note
->off_event().buffer());
1193 MidiRegionView::note_in_region_range(const boost::shared_ptr
<NoteType
> note
, bool& visible
) const
1195 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1197 bool outside
= (note_start_frames
- _region
->start() >= _region
->length()) ||
1198 (note_start_frames
< _region
->start());
1200 visible
= (note
->note() >= midi_stream_view()->lowest_note()) &&
1201 (note
->note() <= midi_stream_view()->highest_note());
1207 MidiRegionView::update_note (CanvasNote
* ev
)
1209 boost::shared_ptr
<NoteType
> note
= ev
->note();
1211 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1212 const nframes64_t note_end_frames
= beats_to_frames(note
->end_time());
1214 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1215 const double y1
= midi_stream_view()->note_to_y(note
->note());
1216 const double note_endpixel
=
1217 trackview
.editor().frame_to_pixel(note_end_frames
- _region
->start());
1219 ev
->property_x1() = x
;
1220 ev
->property_y1() = y1
;
1221 if (note
->length() > 0) {
1222 ev
->property_x2() = note_endpixel
;
1224 ev
->property_x2() = trackview
.editor().frame_to_pixel(_region
->length());
1226 ev
->property_y2() = y1
+ floor(midi_stream_view()->note_height());
1228 if (note
->length() == 0) {
1229 if (_active_notes
) {
1230 assert(note
->note() < 128);
1231 // If this note is already active there's a stuck note,
1232 // finish the old note rectangle
1233 if (_active_notes
[note
->note()]) {
1234 CanvasNote
* const old_rect
= _active_notes
[note
->note()];
1235 boost::shared_ptr
<NoteType
> old_note
= old_rect
->note();
1236 old_rect
->property_x2() = x
;
1237 old_rect
->property_outline_what() = (guint32
) 0xF;
1239 _active_notes
[note
->note()] = ev
;
1241 /* outline all but right edge */
1242 ev
->property_outline_what() = (guint32
) (0x1 & 0x4 & 0x8);
1244 /* outline all edges */
1245 ev
->property_outline_what() = (guint32
) 0xF;
1250 MidiRegionView::update_hit (CanvasHit
* ev
)
1252 boost::shared_ptr
<NoteType
> note
= ev
->note();
1254 const nframes64_t note_start_frames
= beats_to_frames(note
->time());
1255 const double x
= trackview
.editor().frame_to_pixel(note_start_frames
- _region
->start());
1256 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1257 const double y
= midi_stream_view()->note_to_y(note
->note()) + ((diamond_size
-2) / 4.0);
1262 /** Add a MIDI note to the view (with length).
1264 * If in sustained mode, notes with length 0 will be considered active
1265 * notes, and resolve_note should be called when the corresponding note off
1266 * event arrives, to properly display the note.
1269 MidiRegionView::add_note(const boost::shared_ptr
<NoteType
> note
, bool visible
)
1271 CanvasNoteEvent
* event
= 0;
1273 assert(note
->time() >= 0);
1274 assert(midi_view()->note_mode() == Sustained
|| midi_view()->note_mode() == Percussive
);
1276 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1278 if (midi_view()->note_mode() == Sustained
) {
1280 CanvasNote
* ev_rect
= new CanvasNote(*this, *group
, note
);
1282 update_note (ev_rect
);
1286 MidiGhostRegion
* gr
;
1288 for (std::vector
<GhostRegion
*>::iterator g
= ghosts
.begin(); g
!= ghosts
.end(); ++g
) {
1289 if ((gr
= dynamic_cast<MidiGhostRegion
*>(*g
)) != 0) {
1290 gr
->add_note(ev_rect
);
1294 } else if (midi_view()->note_mode() == Percussive
) {
1296 const double diamond_size
= midi_stream_view()->note_height() / 2.0;
1298 CanvasHit
* ev_diamond
= new CanvasHit(*this, *group
, diamond_size
, note
);
1300 update_hit (ev_diamond
);
1309 if (_marked_for_selection
.find(note
) != _marked_for_selection
.end()) {
1310 note_selected(event
, true);
1313 if (_marked_for_velocity
.find(note
) != _marked_for_velocity
.end()) {
1314 event
->show_velocity();
1316 event
->on_channel_selection_change(_last_channel_selection
);
1317 _events
.push_back(event
);
1328 MidiRegionView::add_note (uint8_t channel
, uint8_t number
, uint8_t velocity
,
1329 Evoral::MusicalTime pos
, Evoral::MusicalTime len
)
1331 boost::shared_ptr
<NoteType
> new_note (new NoteType (channel
, pos
, len
, number
, velocity
));
1333 start_delta_command (_("step add"));
1334 delta_add_note (new_note
, true, false);
1337 /* potentially extend region to hold new note */
1339 nframes64_t end_frame
= _region
->position() + beats_to_frames (new_note
->end_time());
1340 nframes64_t region_end
= _region
->position() + _region
->length() - 1;
1342 if (end_frame
> region_end
) {
1343 _region
->set_length (end_frame
, this);
1350 MidiRegionView::add_pgm_change(PCEvent
& program
, const string
& displaytext
)
1352 assert(program
.time
>= 0);
1354 ArdourCanvas::Group
* const group
= (ArdourCanvas::Group
*)get_canvas_group();
1355 const double x
= trackview
.editor().frame_to_pixel(beats_to_frames(program
.time
));
1357 double height
= midi_stream_view()->contents_height();
1359 boost::shared_ptr
<CanvasProgramChange
> pgm_change
= boost::shared_ptr
<CanvasProgramChange
>(
1360 new CanvasProgramChange(*this, *group
,
1365 _custom_device_mode
,
1366 program
.time
, program
.channel
, program
.value
));
1368 // Show unless program change is beyond the region bounds
1369 if (program
.time
- _region
->start() >= _region
->length() || program
.time
< _region
->start()) {
1375 _pgm_changes
.push_back(pgm_change
);
1379 MidiRegionView::get_patch_key_at(double time
, uint8_t channel
, MIDI::Name::PatchPrimaryKey
& key
)
1381 cerr
<< "getting patch key at " << time
<< " for channel " << channel
<< endl
;
1382 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, channel
, MIDI_CTL_MSB_BANK
);
1383 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1385 if (msb_control
!= 0) {
1386 msb
= int(msb_control
->get_float(true, time
));
1387 cerr
<< "got msb " << msb
;
1390 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, channel
, MIDI_CTL_LSB_BANK
);
1391 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1393 if (lsb_control
!= 0) {
1394 lsb
= lsb_control
->get_float(true, time
);
1395 cerr
<< " got lsb " << lsb
;
1398 Evoral::Parameter
program_change(MidiPgmChangeAutomation
, channel
, 0);
1399 boost::shared_ptr
<Evoral::Control
> program_control
= _model
->control(program_change
);
1400 float program_number
= -1.0;
1401 if (program_control
!= 0) {
1402 program_number
= program_control
->get_float(true, time
);
1403 cerr
<< " got program " << program_number
<< endl
;
1406 key
.msb
= (int) floor(msb
+ 0.5);
1407 key
.lsb
= (int) floor(lsb
+ 0.5);
1408 key
.program_number
= (int) floor(program_number
+ 0.5);
1409 assert(key
.is_sane());
1414 MidiRegionView::alter_program_change(PCEvent
& old_program
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1416 // TODO: Get the real event here and alter them at the original times
1417 Evoral::Parameter
bank_select_msb(MidiCCAutomation
, old_program
.channel
, MIDI_CTL_MSB_BANK
);
1418 boost::shared_ptr
<Evoral::Control
> msb_control
= _model
->control(bank_select_msb
);
1419 if (msb_control
!= 0) {
1420 msb_control
->set_float(float(new_patch
.msb
), true, old_program
.time
);
1423 // TODO: Get the real event here and alter them at the original times
1424 Evoral::Parameter
bank_select_lsb(MidiCCAutomation
, old_program
.channel
, MIDI_CTL_LSB_BANK
);
1425 boost::shared_ptr
<Evoral::Control
> lsb_control
= _model
->control(bank_select_lsb
);
1426 if (lsb_control
!= 0) {
1427 lsb_control
->set_float(float(new_patch
.lsb
), true, old_program
.time
);
1430 Evoral::Parameter
program_change(MidiPgmChangeAutomation
, old_program
.channel
, 0);
1431 boost::shared_ptr
<Evoral::Control
> program_control
= _model
->control(program_change
);
1433 assert(program_control
!= 0);
1434 program_control
->set_float(float(new_patch
.program_number
), true, old_program
.time
);
1440 MidiRegionView::program_selected(CanvasProgramChange
& program
, const MIDI::Name::PatchPrimaryKey
& new_patch
)
1442 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1443 alter_program_change(program_change_event
, new_patch
);
1447 MidiRegionView::previous_program(CanvasProgramChange
& program
)
1449 MIDI::Name::PatchPrimaryKey key
;
1450 get_patch_key_at(program
.event_time(), program
.channel(), key
);
1452 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1453 MIDI::Name::MidiPatchManager::instance().previous_patch(
1455 _custom_device_mode
,
1459 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1461 alter_program_change(program_change_event
, patch
->patch_primary_key());
1466 MidiRegionView::next_program(CanvasProgramChange
& program
)
1468 MIDI::Name::PatchPrimaryKey key
;
1469 get_patch_key_at(program
.event_time(), program
.channel(), key
);
1471 boost::shared_ptr
<MIDI::Name::Patch
> patch
=
1472 MIDI::Name::MidiPatchManager::instance().next_patch(
1474 _custom_device_mode
,
1478 PCEvent
program_change_event(program
.event_time(), program
.program(), program
.channel());
1480 alter_program_change(program_change_event
, patch
->patch_primary_key());
1485 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent
* cne
)
1487 if (_selection
.empty()) {
1491 if (_selection
.erase (cne
) > 0) {
1492 cerr
<< "Erased a CNE from selection\n";
1497 MidiRegionView::delete_selection()
1499 if (_selection
.empty()) {
1503 start_delta_command (_("delete selection"));
1505 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1506 if ((*i
)->selected()) {
1507 _delta_command
->remove((*i
)->note());
1517 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent
* ev
)
1519 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1520 if ((*i
)->selected() && (*i
) != ev
) {
1521 (*i
)->selected(false);
1522 (*i
)->hide_velocity();
1530 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent
* ev
)
1532 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
1535 Selection::iterator tmp
= i
;
1538 (*i
)->selected (false);
1539 _selection
.erase (i
);
1548 /* don't bother with removing this regionview from the editor selection,
1549 since we're about to add another note, and thus put/keep this
1550 regionview in the editor selection.
1553 if (!ev
->selected()) {
1554 add_to_selection (ev
);
1559 MidiRegionView::select_matching_notes (uint8_t notenum
, uint16_t channel_mask
, bool add
, bool extend
)
1561 uint8_t low_note
= 127;
1562 uint8_t high_note
= 0;
1563 MidiModel::Notes
& notes (_model
->notes());
1564 _optimization_iterator
= _events
.begin();
1566 if (extend
&& _selection
.empty()) {
1572 /* scan existing selection to get note range */
1574 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1575 if ((*i
)->note()->note() < low_note
) {
1576 low_note
= (*i
)->note()->note();
1578 if ((*i
)->note()->note() > high_note
) {
1579 high_note
= (*i
)->note()->note();
1583 low_note
= min (low_note
, notenum
);
1584 high_note
= max (high_note
, notenum
);
1587 no_sound_notes
= true;
1589 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1591 boost::shared_ptr
<NoteType
> note (*n
);
1592 CanvasNoteEvent
* cne
;
1593 bool select
= false;
1595 if (((0x0001 << note
->channel()) & channel_mask
) != 0) {
1597 if ((note
->note() >= low_note
&& note
->note() <= high_note
)) {
1600 } else if (note
->note() == notenum
) {
1606 if ((cne
= find_canvas_note (note
)) != 0) {
1607 // extend is false because we've taken care of it,
1608 // since it extends by time range, not pitch.
1609 note_selected (cne
, add
, false);
1613 add
= true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1617 no_sound_notes
= false;
1621 MidiRegionView::toggle_matching_notes (uint8_t notenum
, uint16_t channel_mask
)
1623 MidiModel::Notes
& notes (_model
->notes());
1624 _optimization_iterator
= _events
.begin();
1626 for (MidiModel::Notes::iterator n
= notes
.begin(); n
!= notes
.end(); ++n
) {
1628 boost::shared_ptr
<NoteType
> note (*n
);
1629 CanvasNoteEvent
* cne
;
1631 if (note
->note() == notenum
&& (((0x0001 << note
->channel()) & channel_mask
) != 0)) {
1632 if ((cne
= find_canvas_note (note
)) != 0) {
1633 if (cne
->selected()) {
1634 note_deselected (cne
);
1636 note_selected (cne
, true, false);
1644 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent
* ev
, bool add
, bool extend
)
1647 clear_selection_except(ev
);
1652 if (!ev
->selected()) {
1653 add_to_selection (ev
);
1657 /* find end of latest note selected, select all between that and the start of "ev" */
1659 Evoral::MusicalTime earliest
= DBL_MAX
;
1660 Evoral::MusicalTime latest
= 0;
1662 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1663 if ((*i
)->note()->end_time() > latest
) {
1664 latest
= (*i
)->note()->end_time();
1666 if ((*i
)->note()->time() < earliest
) {
1667 earliest
= (*i
)->note()->time();
1671 if (ev
->note()->end_time() > latest
) {
1672 latest
= ev
->note()->end_time();
1675 if (ev
->note()->time() < earliest
) {
1676 earliest
= ev
->note()->time();
1679 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1681 /* find notes entirely within OR spanning the earliest..latest range */
1683 if (((*i
)->note()->time() >= earliest
&& (*i
)->note()->end_time() <= latest
) ||
1684 ((*i
)->note()->time() <= earliest
&& (*i
)->note()->end_time() >= latest
)) {
1685 add_to_selection (*i
);
1689 /* if events were guaranteed to be time sorted, we could do this.
1690 but as of sept 10th 2009, they no longer are.
1693 if ((*i
)->note()->time() > latest
) {
1702 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent
* ev
)
1704 remove_from_selection (ev
);
1708 MidiRegionView::update_drag_selection(double x1
, double x2
, double y1
, double y2
)
1718 // TODO: Make this faster by storing the last updated selection rect, and only
1719 // adjusting things that are in the area that appears/disappeared.
1720 // We probably need a tree to be able to find events in O(log(n)) time.
1722 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
1724 /* check if any corner of the note is inside the rect
1727 1) this is computing "touched by", not "contained by" the rect.
1728 2) this does not require that events be sorted in time.
1731 const double ix1
= (*i
)->x1();
1732 const double ix2
= (*i
)->x2();
1733 const double iy1
= (*i
)->y1();
1734 const double iy2
= (*i
)->y2();
1736 if ((ix1
>= x1
&& ix1
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
1737 (ix1
>= x1
&& ix1
<= x2
&& iy2
>= y1
&& iy2
<= y2
) ||
1738 (ix2
>= x1
&& ix2
<= x2
&& iy1
>= y1
&& iy1
<= y2
) ||
1739 (ix2
>= x1
&& ix2
<= x2
&& iy2
>= y1
&& iy2
<= y2
)) {
1742 if (!(*i
)->selected()) {
1743 add_to_selection (*i
);
1745 } else if ((*i
)->selected()) {
1746 // Not inside rectangle
1747 remove_from_selection (*i
);
1753 MidiRegionView::remove_from_selection (CanvasNoteEvent
* ev
)
1755 Selection::iterator i
= _selection
.find (ev
);
1757 if (i
!= _selection
.end()) {
1758 _selection
.erase (i
);
1761 ev
->selected (false);
1762 ev
->hide_velocity ();
1764 if (_selection
.empty()) {
1765 PublicEditor
& editor (trackview
.editor());
1766 editor
.get_selection().remove (this);
1771 MidiRegionView::add_to_selection (CanvasNoteEvent
* ev
)
1773 bool add_mrv_selection
= false;
1775 if (_selection
.empty()) {
1776 add_mrv_selection
= true;
1779 if (_selection
.insert (ev
).second
) {
1780 ev
->selected (true);
1781 play_midi_note ((ev
)->note());
1784 if (add_mrv_selection
) {
1785 PublicEditor
& editor (trackview
.editor());
1786 editor
.get_selection().add (this);
1791 MidiRegionView::move_selection(double dx
, double dy
)
1793 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1794 (*i
)->move_event(dx
, dy
);
1799 MidiRegionView::note_dropped(CanvasNoteEvent
*, double dt
, int8_t dnote
)
1801 assert (!_selection
.empty());
1803 uint8_t lowest_note_in_selection
= 127;
1804 uint8_t highest_note_in_selection
= 0;
1805 uint8_t highest_note_difference
= 0;
1807 // find highest and lowest notes first
1809 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1810 uint8_t pitch
= (*i
)->note()->note();
1811 lowest_note_in_selection
= std::min(lowest_note_in_selection
, pitch
);
1812 highest_note_in_selection
= std::max(highest_note_in_selection
, pitch
);
1816 cerr << "dnote: " << (int) dnote << endl;
1817 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1818 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1819 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1820 << int(highest_note_in_selection) << endl;
1821 cerr << "selection size: " << _selection.size() << endl;
1822 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1825 // Make sure the note pitch does not exceed the MIDI standard range
1826 if (highest_note_in_selection
+ dnote
> 127) {
1827 highest_note_difference
= highest_note_in_selection
- 127;
1830 start_diff_command(_("move notes"));
1832 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end() ; ++i
) {
1834 nframes64_t start_frames
= beats_to_frames((*i
)->note()->time());
1837 start_frames
+= snap_frame_to_frame(trackview
.editor().pixel_to_frame(dt
));
1839 start_frames
-= snap_frame_to_frame(trackview
.editor().pixel_to_frame(-dt
));
1842 Evoral::MusicalTime new_time
= frames_to_beats(start_frames
);
1848 diff_add_change (*i
, MidiModel::DiffCommand::StartTime
, new_time
);
1850 uint8_t original_pitch
= (*i
)->note()->note();
1851 uint8_t new_pitch
= original_pitch
+ dnote
- highest_note_difference
;
1853 // keep notes in standard midi range
1854 clamp_to_0_127(new_pitch
);
1856 // keep original pitch if note is dragged outside valid midi range
1857 if ((original_pitch
!= 0 && new_pitch
== 0)
1858 || (original_pitch
!= 127 && new_pitch
== 127)) {
1859 new_pitch
= original_pitch
;
1862 lowest_note_in_selection
= std::min(lowest_note_in_selection
, new_pitch
);
1863 highest_note_in_selection
= std::max(highest_note_in_selection
, new_pitch
);
1865 diff_add_change (*i
, MidiModel::DiffCommand::NoteNumber
, new_pitch
);
1870 // care about notes being moved beyond the upper/lower bounds on the canvas
1871 if (lowest_note_in_selection
< midi_stream_view()->lowest_note() ||
1872 highest_note_in_selection
> midi_stream_view()->highest_note()) {
1873 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange
);
1878 MidiRegionView::snap_pixel_to_frame(double x
)
1880 PublicEditor
& editor
= trackview
.editor();
1881 // x is region relative, convert it to global absolute frames
1882 nframes64_t frame
= editor
.pixel_to_frame(x
) + _region
->position();
1883 editor
.snap_to(frame
);
1884 return frame
- _region
->position(); // convert back to region relative
1888 MidiRegionView::snap_frame_to_frame(nframes64_t x
)
1890 PublicEditor
& editor
= trackview
.editor();
1891 // x is region relative, convert it to global absolute frames
1892 nframes64_t frame
= x
+ _region
->position();
1893 editor
.snap_to(frame
);
1894 return frame
- _region
->position(); // convert back to region relative
1898 MidiRegionView::snap_to_pixel(double x
)
1900 return (double) trackview
.editor().frame_to_pixel(snap_pixel_to_frame(x
));
1904 MidiRegionView::get_position_pixels()
1906 nframes64_t region_frame
= get_position();
1907 return trackview
.editor().frame_to_pixel(region_frame
);
1911 MidiRegionView::get_end_position_pixels()
1913 nframes64_t frame
= get_position() + get_duration ();
1914 return trackview
.editor().frame_to_pixel(frame
);
1918 MidiRegionView::beats_to_frames(double beats
) const
1920 return _time_converter
.to(beats
);
1924 MidiRegionView::frames_to_beats(nframes64_t frames
) const
1926 return _time_converter
.from(frames
);
1930 MidiRegionView::begin_resizing (bool /*at_front*/)
1932 _resize_data
.clear();
1934 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
1935 CanvasNote
*note
= dynamic_cast<CanvasNote
*> (*i
);
1937 // only insert CanvasNotes into the map
1939 NoteResizeData
*resize_data
= new NoteResizeData();
1940 resize_data
->canvas_note
= note
;
1942 // create a new SimpleRect from the note which will be the resize preview
1943 SimpleRect
*resize_rect
= new SimpleRect(
1944 *group
, note
->x1(), note
->y1(), note
->x2(), note
->y2());
1946 // calculate the colors: get the color settings
1947 uint32_t fill_color
= UINT_RGBA_CHANGE_A(
1948 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get(),
1951 // make the resize preview notes more transparent and bright
1952 fill_color
= UINT_INTERPOLATE(fill_color
, 0xFFFFFF40, 0.5);
1954 // calculate color based on note velocity
1955 resize_rect
->property_fill_color_rgba() = UINT_INTERPOLATE(
1956 CanvasNoteEvent::meter_style_fill_color(note
->note()->velocity()),
1960 resize_rect
->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1961 ARDOUR_UI::config()->canvasvar_MidiNoteSelected
.get());
1963 resize_data
->resize_rect
= resize_rect
;
1964 _resize_data
.push_back(resize_data
);
1970 MidiRegionView::update_resizing (bool at_front
, double delta_x
, bool relative
)
1972 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
1973 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
1974 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
1979 current_x
= canvas_note
->x1() + delta_x
;
1981 // x is in track relative, transform it to region relative
1982 current_x
= delta_x
- get_position_pixels();
1986 current_x
= canvas_note
->x2() + delta_x
;
1988 // x is in track relative, transform it to region relative
1989 current_x
= delta_x
- get_end_position_pixels ();
1994 resize_rect
->property_x1() = snap_to_pixel(current_x
);
1995 resize_rect
->property_x2() = canvas_note
->x2();
1997 resize_rect
->property_x2() = snap_to_pixel(current_x
);
1998 resize_rect
->property_x1() = canvas_note
->x1();
2004 MidiRegionView::commit_resizing (bool at_front
, double delta_x
, bool relative
)
2006 start_diff_command(_("resize notes"));
2008 for (std::vector
<NoteResizeData
*>::iterator i
= _resize_data
.begin(); i
!= _resize_data
.end(); ++i
) {
2009 CanvasNote
* canvas_note
= (*i
)->canvas_note
;
2010 SimpleRect
* resize_rect
= (*i
)->resize_rect
;
2011 const double region_start
= get_position_pixels();
2016 current_x
= canvas_note
->x1() + delta_x
;
2018 // x is in track relative, transform it to region relative
2019 current_x
= region_start
+ delta_x
;
2023 current_x
= canvas_note
->x2() + delta_x
;
2025 // x is in track relative, transform it to region relative
2026 current_x
= region_start
+ delta_x
;
2030 current_x
= snap_pixel_to_frame (current_x
);
2031 current_x
= frames_to_beats (current_x
);
2033 if (at_front
&& current_x
< canvas_note
->note()->end_time()) {
2034 diff_add_change (canvas_note
, MidiModel::DiffCommand::StartTime
, current_x
);
2036 double len
= canvas_note
->note()->time() - current_x
;
2037 len
+= canvas_note
->note()->length();
2040 /* XXX convert to beats */
2041 diff_add_change (canvas_note
, MidiModel::DiffCommand::Length
, len
);
2046 double len
= current_x
- canvas_note
->note()->time();
2049 /* XXX convert to beats */
2050 diff_add_change (canvas_note
, MidiModel::DiffCommand::Length
, len
);
2058 _resize_data
.clear();
2063 MidiRegionView::change_note_velocity(CanvasNoteEvent
* event
, int8_t velocity
, bool relative
)
2065 uint8_t new_velocity
;
2068 new_velocity
= event
->note()->velocity() + velocity
;
2069 clamp_to_0_127(new_velocity
);
2071 new_velocity
= velocity
;
2074 diff_add_change (event
, MidiModel::DiffCommand::Velocity
, new_velocity
);
2078 MidiRegionView::change_note_note (CanvasNoteEvent
* event
, int8_t note
, bool relative
)
2083 new_note
= event
->note()->note() + note
;
2088 clamp_to_0_127 (new_note
);
2089 diff_add_change (event
, MidiModel::DiffCommand::NoteNumber
, new_note
);
2093 MidiRegionView::trim_note (CanvasNoteEvent
* event
, Evoral::MusicalTime front_delta
, Evoral::MusicalTime end_delta
)
2095 bool change_start
= false;
2096 bool change_length
= false;
2097 Evoral::MusicalTime new_start
;
2098 Evoral::MusicalTime new_length
;
2100 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2102 front_delta: if positive - move the start of the note later in time (shortening it)
2103 if negative - move the start of the note earlier in time (lengthening it)
2105 end_delta: if positive - move the end of the note later in time (lengthening it)
2106 if negative - move the end of the note earlier in time (shortening it)
2110 if (front_delta
< 0) {
2112 if (event
->note()->time() < -front_delta
) {
2115 new_start
= event
->note()->time() + front_delta
; // moves earlier
2118 /* start moved toward zero, so move the end point out to where it used to be.
2119 Note that front_delta is negative, so this increases the length.
2122 new_length
= event
->note()->length() - front_delta
;
2123 change_start
= true;
2124 change_length
= true;
2128 Evoral::MusicalTime new_pos
= event
->note()->time() + front_delta
;
2130 if (new_pos
< event
->note()->end_time()) {
2131 new_start
= event
->note()->time() + front_delta
;
2132 /* start moved toward the end, so move the end point back to where it used to be */
2133 new_length
= event
->note()->length() - front_delta
;
2134 change_start
= true;
2135 change_length
= true;
2142 bool can_change
= true;
2143 if (end_delta
< 0) {
2144 if (event
->note()->length() < -end_delta
) {
2150 new_length
= event
->note()->length() + end_delta
;
2151 change_length
= true;
2156 diff_add_change (event
, MidiModel::DiffCommand::StartTime
, new_start
);
2159 if (change_length
) {
2160 diff_add_change (event
, MidiModel::DiffCommand::Length
, new_length
);
2165 MidiRegionView::change_note_time (CanvasNoteEvent
* event
, Evoral::MusicalTime delta
, bool relative
)
2167 Evoral::MusicalTime new_time
;
2171 if (event
->note()->time() < -delta
) {
2174 new_time
= event
->note()->time() + delta
;
2177 new_time
= event
->note()->time() + delta
;
2183 diff_add_change (event
, MidiModel::DiffCommand::StartTime
, new_time
);
2187 MidiRegionView::change_velocities (bool up
, bool fine
, bool allow_smush
)
2191 if (_selection
.empty()) {
2206 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2207 if ((*i
)->note()->velocity() + delta
== 0 || (*i
)->note()->velocity() + delta
== 127) {
2213 start_diff_command(_("change velocities"));
2215 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end();) {
2216 Selection::iterator next
= i
;
2218 change_note_velocity (*i
, delta
, true);
2227 MidiRegionView::transpose (bool up
, bool fine
, bool allow_smush
)
2229 if (_selection
.empty()) {
2246 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2248 if ((int8_t) (*i
)->note()->note() + delta
<= 0) {
2252 if ((int8_t) (*i
)->note()->note() + delta
> 127) {
2259 start_diff_command (_("transpose"));
2261 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2262 Selection::iterator next
= i
;
2264 change_note_note (*i
, delta
, true);
2272 MidiRegionView::change_note_lengths (bool fine
, bool shorter
, bool start
, bool end
)
2274 Evoral::MusicalTime delta
;
2279 /* grab the current grid distance */
2281 delta
= trackview
.editor().get_grid_type_as_beats (success
, _region
->position());
2283 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2284 cerr
<< "Grid type not available as beats - TO BE FIXED\n";
2293 start_diff_command (_("change note lengths"));
2295 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2296 Selection::iterator next
= i
;
2299 /* note the negation of the delta for start */
2301 trim_note (*i
, (start
? -delta
: 0), (end
? delta
: 0));
2310 MidiRegionView::nudge_notes (bool forward
)
2312 if (_selection
.empty()) {
2316 /* pick a note as the point along the timeline to get the nudge distance.
2317 its not necessarily the earliest note, so we may want to pull the notes out
2318 into a vector and sort before using the first one.
2321 nframes64_t ref_point
= _region
->position() + beats_to_frames ((*(_selection
.begin()))->note()->time());
2323 nframes64_t distance
;
2325 if (trackview
.editor().snap_mode() == Editing::SnapOff
) {
2327 /* grid is off - use nudge distance */
2329 distance
= trackview
.editor().get_nudge_distance (ref_point
, unused
);
2335 nframes64_t next_pos
= ref_point
;
2338 /* XXX need check on max_frames, but that needs max_frames64 or something */
2341 if (next_pos
== 0) {
2347 cerr
<< "ref point was " << ref_point
<< " next was " << next_pos
;
2348 trackview
.editor().snap_to (next_pos
, (forward
? 1 : -1), false);
2349 distance
= ref_point
- next_pos
;
2350 cerr
<< " final is " << next_pos
<< " distance = " << distance
<< endl
;
2353 if (distance
== 0) {
2357 Evoral::MusicalTime delta
= frames_to_beats (fabs (distance
));
2363 start_diff_command (_("nudge"));
2365 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ) {
2366 Selection::iterator next
= i
;
2368 change_note_time (*i
, delta
, true);
2376 MidiRegionView::change_channel(uint8_t channel
)
2378 start_diff_command(_("change channel"));
2379 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2380 diff_add_change (*i
, MidiModel::DiffCommand::Channel
, channel
);
2387 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent
* ev
)
2389 if (_mouse_state
== SelectTouchDragging
) {
2390 note_selected(ev
, true);
2394 snprintf (buf
, sizeof (buf
), "%d", (int) ev
->note()->note());
2395 // This causes an infinite loop on note add sometimes
2396 //PublicEditor& editor (trackview.editor());
2397 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2398 //editor.show_verbose_canvas_cursor_with (buf);
2402 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent
*)
2404 PublicEditor
& editor (trackview
.editor());
2405 editor
.hide_verbose_canvas_cursor ();
2410 MidiRegionView::switch_source(boost::shared_ptr
<Source
> src
)
2412 boost::shared_ptr
<MidiSource
> msrc
= boost::dynamic_pointer_cast
<MidiSource
>(src
);
2414 display_model(msrc
->model());
2418 MidiRegionView::set_frame_color()
2421 if (_selected
&& should_show_selection
) {
2422 frame
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase
.get();
2424 frame
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase
.get();
2430 MidiRegionView::midi_channel_mode_changed(ChannelMode mode
, uint16_t mask
)
2434 case FilterChannels
:
2435 _force_channel
= -1;
2438 _force_channel
= mask
;
2439 mask
= 0xFFFF; // Show all notes as active (below)
2442 // Update notes for selection
2443 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2444 (*i
)->on_channel_selection_change(mask
);
2447 _last_channel_selection
= mask
;
2451 MidiRegionView::midi_patch_settings_changed(std::string model
, std::string custom_device_mode
)
2453 _model_name
= model
;
2454 _custom_device_mode
= custom_device_mode
;
2459 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op
)
2461 if (_selection
.empty()) {
2465 PublicEditor
& editor (trackview
.editor());
2470 editor
.get_cut_buffer().add (selection_as_cut_buffer());
2478 start_delta_command();
2480 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2486 delta_remove_note (*i
);
2496 MidiRegionView::selection_as_cut_buffer () const
2500 for (Selection::iterator i
= _selection
.begin(); i
!= _selection
.end(); ++i
) {
2501 NoteType
* n
= (*i
)->note().get();
2502 notes
.insert (boost::shared_ptr
<NoteType
> (new NoteType (*n
)));
2505 MidiCutBuffer
* cb
= new MidiCutBuffer (trackview
.session());
2512 MidiRegionView::paste (nframes64_t pos
, float times
, const MidiCutBuffer
& mcb
)
2518 start_delta_command (_("paste"));
2520 Evoral::MusicalTime beat_delta
;
2521 Evoral::MusicalTime paste_pos_beats
;
2522 Evoral::MusicalTime duration
;
2523 Evoral::MusicalTime end_point
;
2525 duration
= (*mcb
.notes().rbegin())->end_time() - (*mcb
.notes().begin())->time();
2526 paste_pos_beats
= frames_to_beats (pos
- _region
->position());
2527 beat_delta
= (*mcb
.notes().begin())->time() - paste_pos_beats
;
2528 paste_pos_beats
= 0;
2530 _selection
.clear ();
2532 for (int n
= 0; n
< (int) times
; ++n
) {
2534 cerr
<< "Pasting " << mcb
.notes().size() << " for the " << n
+1 << "th time\n";
2536 for (Notes::const_iterator i
= mcb
.notes().begin(); i
!= mcb
.notes().end(); ++i
) {
2538 boost::shared_ptr
<NoteType
> copied_note (new NoteType (*((*i
).get())));
2539 copied_note
->set_time (paste_pos_beats
+ copied_note
->time() - beat_delta
);
2541 /* make all newly added notes selected */
2543 delta_add_note (copied_note
, true);
2544 end_point
= copied_note
->end_time();
2547 paste_pos_beats
+= duration
;
2550 /* if we pasted past the current end of the region, extend the region */
2552 nframes64_t end_frame
= _region
->position() + beats_to_frames (end_point
);
2553 nframes64_t region_end
= _region
->position() + _region
->length() - 1;
2555 if (end_frame
> region_end
) {
2557 cerr
<< "region end is now " << end_frame
<< " to extend from " << region_end
<< endl
;
2559 trackview
.session()->begin_reversible_command (_("paste"));
2561 _region
->clear_history ();
2562 _region
->set_length (end_frame
, this);
2563 trackview
.session()->add_command (new StatefulDiffCommand (_region
));
2566 cerr
<< "region end finally at " << _region
->position() + _region
->length() - 1;
2570 struct EventNoteTimeEarlyFirstComparator
{
2571 bool operator() (CanvasNoteEvent
* a
, CanvasNoteEvent
* b
) {
2572 return a
->note()->time() < b
->note()->time();
2577 MidiRegionView::time_sort_events ()
2579 if (!_sort_needed
) {
2583 EventNoteTimeEarlyFirstComparator cmp
;
2586 _sort_needed
= false;
2590 MidiRegionView::goto_next_note ()
2592 // nframes64_t pos = -1;
2593 bool use_next
= false;
2595 if (_events
.back()->selected()) {
2599 time_sort_events ();
2601 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2602 if ((*i
)->selected()) {
2605 } else if (use_next
) {
2607 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2612 /* use the first one */
2614 unique_select (_events
.front());
2619 MidiRegionView::goto_previous_note ()
2621 // nframes64_t pos = -1;
2622 bool use_next
= false;
2624 if (_events
.front()->selected()) {
2628 time_sort_events ();
2630 for (Events::reverse_iterator i
= _events
.rbegin(); i
!= _events
.rend(); ++i
) {
2631 if ((*i
)->selected()) {
2634 } else if (use_next
) {
2636 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2641 /* use the last one */
2643 unique_select (*(_events
.rbegin()));
2647 MidiRegionView::selection_as_notelist (Notes
& selected
)
2649 time_sort_events ();
2651 for (Events::iterator i
= _events
.begin(); i
!= _events
.end(); ++i
) {
2652 if ((*i
)->selected()) {
2653 selected
.insert ((*i
)->note());