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"
44 #include "evoral/midi_util.h"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas-program-change.h"
51 #include "ghostregion.h"
52 #include "gui_thread.h"
54 #include "midi_cut_buffer.h"
55 #include "midi_list_editor.h"
56 #include "midi_region_view.h"
57 #include "midi_streamview.h"
58 #include "midi_time_axis.h"
59 #include "midi_time_axis.h"
60 #include "midi_util.h"
61 #include "public_editor.h"
62 #include "selection.h"
63 #include "simpleline.h"
64 #include "streamview.h"
69 using namespace ARDOUR;
71 using namespace Editing;
72 using namespace ArdourCanvas;
73 using Gtkmm2ext::Keyboard;
75 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
76 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
77 : RegionView (parent, tv, r, spu, basic_color)
79 , _last_channel_selection(0xFFFF)
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))
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))
117 , _sort_needed (true)
118 , _optimization_iterator (_events.end())
120 , no_sound_notes (false)
122 _note_group->raise_to_top();
123 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
126 MidiRegionView::MidiRegionView (const MidiRegionView& other)
127 : sigc::trackable(other)
130 , _last_channel_selection(0xFFFF)
131 , _model_name(string())
132 , _custom_device_mode(string())
134 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
141 , _sort_needed (true)
142 , _optimization_iterator (_events.end())
144 , no_sound_notes (false)
149 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
150 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
155 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
156 : RegionView (other, boost::shared_ptr<Region> (region))
158 , _last_channel_selection(0xFFFF)
159 , _model_name(string())
160 , _custom_device_mode(string())
162 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
169 , _sort_needed (true)
170 , _optimization_iterator (_events.end())
172 , no_sound_notes (false)
177 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
178 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
184 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
186 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
188 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
189 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
193 midi_region()->midi_source(0)->load_model();
196 _model = midi_region()->midi_source(0)->model();
197 _enable_display = false;
199 RegionView::init (basic_color, false);
201 compute_colors (basic_color);
203 set_height (trackview.current_height());
206 region_sync_changed ();
207 region_resized (ARDOUR::bounds_change);
210 reset_width_dependent_items (_pixel_width);
214 _enable_display = true;
217 display_model (_model);
221 group->raise_to_top();
222 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
224 midi_view()->signal_channel_mode_changed().connect(
225 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
227 midi_view()->signal_midi_patch_settings_changed().connect(
228 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
230 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
234 MidiRegionView::canvas_event(GdkEvent* ev)
236 if (!trackview.editor().internal_editing()) {
240 /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
241 to its items, which means that ev->type == GDK_SCROLL will never be seen
246 return scroll (&ev->scroll);
249 return key_press (&ev->key);
251 case GDK_KEY_RELEASE:
252 return key_release (&ev->key);
254 case GDK_BUTTON_PRESS:
255 return button_press (&ev->button);
257 case GDK_2BUTTON_PRESS:
260 case GDK_BUTTON_RELEASE:
261 return button_release (&ev->button);
263 case GDK_ENTER_NOTIFY:
264 return enter_notify (&ev->crossing);
266 case GDK_LEAVE_NOTIFY:
267 return leave_notify (&ev->crossing);
269 case GDK_MOTION_NOTIFY:
270 return motion (&ev->motion);
280 MidiRegionView::enter_notify (GdkEventCrossing* ev)
282 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
284 Keyboard::magic_widget_grab_focus();
287 if (trackview.editor().current_mouse_mode() == MouseRange) {
288 create_ghost_note (ev->x, ev->y);
295 MidiRegionView::leave_notify (GdkEventCrossing* ev)
297 trackview.editor().hide_verbose_canvas_cursor ();
304 MidiRegionView::button_press (GdkEventButton* ev)
308 group->w2i (_last_x, _last_y);
310 if (_mouse_state != SelectTouchDragging && ev->button == 1) {
311 _pressed_button = ev->button;
312 _mouse_state = Pressed;
315 _pressed_button = ev->button;
321 MidiRegionView::button_release (GdkEventButton* ev)
323 double event_x, event_y;
324 nframes64_t event_frame = 0;
328 group->w2i(event_x, event_y);
329 group->ungrab(ev->time);
330 event_frame = trackview.editor().pixel_to_frame(event_x);
332 if (ev->button == 3) {
334 } else if (_pressed_button != 1) {
338 switch (_mouse_state) {
339 case Pressed: // Clicked
340 switch (trackview.editor().current_mouse_mode()) {
344 maybe_select_by_position (ev, event_x, event_y);
350 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
355 create_note_at (event_x, event_y, beats);
363 case SelectRectDragging: // Select drag done
369 case AddDragging: // Add drag done
371 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
372 const double x = _drag_rect->property_x1();
373 const double length = trackview.editor().pixel_to_frame
374 (_drag_rect->property_x2() - _drag_rect->property_x1());
376 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length));
382 create_ghost_note (ev->x, ev->y);
392 MidiRegionView::motion (GdkEventMotion* ev)
394 double event_x, event_y;
395 nframes64_t event_frame = 0;
399 group->w2i(event_x, event_y);
401 // convert event_x to global frame
402 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
403 trackview.editor().snap_to(event_frame);
404 // convert event_frame back to local coordinates relative to position
405 event_frame -= _region->position();
408 update_ghost_note (ev->x, ev->y);
411 /* any motion immediately hides velocity text that may have been visible */
413 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
414 (*i)->hide_velocity ();
417 switch (_mouse_state) {
418 case Pressed: // Maybe start a drag, if we've moved a bit
420 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
421 /* no appreciable movement since the button was pressed */
426 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
427 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
428 Gdk::Cursor(Gdk::FLEUR), ev->time);
431 _drag_start_x = event_x;
432 _drag_start_y = event_y;
434 _drag_rect = new ArdourCanvas::SimpleRect(*group);
435 _drag_rect->property_x1() = event_x;
436 _drag_rect->property_y1() = event_y;
437 _drag_rect->property_x2() = event_x;
438 _drag_rect->property_y2() = event_y;
439 _drag_rect->property_outline_what() = 0xFF;
440 _drag_rect->property_outline_color_rgba()
441 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
442 _drag_rect->property_fill_color_rgba()
443 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
445 _mouse_state = SelectRectDragging;
448 // Add note drag start
449 } else if (trackview.editor().internal_editing()) {
454 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
455 Gdk::Cursor(Gdk::FLEUR), ev->time);
458 _drag_start_x = event_x;
459 _drag_start_y = event_y;
461 _drag_rect = new ArdourCanvas::SimpleRect(*group);
462 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
464 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
465 midi_stream_view()->y_to_note(event_y));
466 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
467 _drag_rect->property_y2() = _drag_rect->property_y1()
468 + floor(midi_stream_view()->note_height());
469 _drag_rect->property_outline_what() = 0xFF;
470 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
471 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
473 _mouse_state = AddDragging;
479 case SelectRectDragging: // Select drag motion
480 case AddDragging: // Add note drag motion
484 GdkModifierType state;
485 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
490 if (_mouse_state == AddDragging)
491 event_x = trackview.editor().frame_to_pixel(event_frame);
494 if (event_x > _drag_start_x)
495 _drag_rect->property_x2() = event_x;
497 _drag_rect->property_x1() = event_x;
500 if (_drag_rect && _mouse_state == SelectRectDragging) {
501 if (event_y > _drag_start_y)
502 _drag_rect->property_y2() = event_y;
504 _drag_rect->property_y1() = event_y;
506 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
512 case SelectTouchDragging:
524 MidiRegionView::scroll (GdkEventScroll* ev)
526 if (_selection.empty()) {
530 trackview.editor().hide_verbose_canvas_cursor ();
532 bool fine = Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
534 if (ev->direction == GDK_SCROLL_UP) {
535 change_velocities (true, fine, false);
536 } else if (ev->direction == GDK_SCROLL_DOWN) {
537 change_velocities (false, fine, false);
543 MidiRegionView::key_press (GdkEventKey* ev)
545 /* since GTK bindings are generally activated on press, and since
546 detectable auto-repeat is the name of the game and only sends
547 repeated presses, carry out key actions at key press, not release.
550 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
551 _mouse_state = SelectTouchDragging;
554 } else if (ev->keyval == GDK_Escape) {
558 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
560 bool start = (ev->keyval == GDK_comma);
561 bool end = (ev->keyval == GDK_period);
562 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
563 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
565 change_note_lengths (fine, shorter, start, end);
569 } else if (ev->keyval == GDK_Delete) {
574 } else if (ev->keyval == GDK_Tab) {
576 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
577 goto_previous_note ();
583 } else if (ev->keyval == GDK_Up) {
585 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
586 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
588 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
589 change_velocities (true, fine, allow_smush);
591 transpose (true, fine, allow_smush);
595 } else if (ev->keyval == GDK_Down) {
597 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
598 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
600 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
601 change_velocities (false, fine, allow_smush);
603 transpose (false, fine, allow_smush);
607 } else if (ev->keyval == GDK_Left) {
612 } else if (ev->keyval == GDK_Right) {
617 } else if (ev->keyval == GDK_Control_L) {
620 } else if (ev->keyval == GDK_r) {
621 /* if we're not step editing, this really doesn't matter */
622 midi_view()->step_edit_rest ();
630 MidiRegionView::key_release (GdkEventKey* ev)
632 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
640 MidiRegionView::show_list_editor ()
643 _list_editor = new MidiListEditor (trackview.session(), midi_region());
645 _list_editor->present ();
648 /** Add a note to the model, and the view, at a canvas (click) coordinate.
649 * \param x horizontal position in pixels
650 * \param y vertical position in pixels
651 * \param length duration of the note in beats */
653 MidiRegionView::create_note_at(double x, double y, double length)
655 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
656 MidiStreamView* const view = mtv->midi_view();
658 double note = midi_stream_view()->y_to_note(y);
661 assert(note <= 127.0);
663 // Start of note in frames relative to region start
664 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
665 assert(start_frames >= 0);
668 length = frames_to_beats(
669 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
671 assert (length != 0);
673 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
674 frames_to_beats(start_frames + _region->start()), length,
675 (uint8_t)note, 0x40));
677 if (_model->contains (new_note)) {
681 view->update_note_range(new_note->note());
683 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
685 _model->apply_command(*trackview.session(), cmd);
687 play_midi_note (new_note);
691 MidiRegionView::clear_events()
696 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
697 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
702 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
707 _pgm_changes.clear();
709 _optimization_iterator = _events.end();
714 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
717 content_connection.disconnect ();
718 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
722 if (_enable_display) {
729 MidiRegionView::start_delta_command(string name)
731 if (!_delta_command) {
732 _delta_command = _model->new_delta_command(name);
737 MidiRegionView::start_diff_command(string name)
739 if (!_diff_command) {
740 _diff_command = _model->new_diff_command(name);
745 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
747 if (_delta_command) {
748 _delta_command->add(note);
751 _marked_for_selection.insert(note);
754 _marked_for_velocity.insert(note);
759 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
761 if (_delta_command && ev->note()) {
762 _delta_command->remove(ev->note());
767 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
768 MidiModel::DiffCommand::Property property,
772 _diff_command->change (ev->note(), property, val);
777 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
778 MidiModel::DiffCommand::Property property,
779 Evoral::MusicalTime val)
782 _diff_command->change (ev->note(), property, val);
787 MidiRegionView::apply_delta()
789 if (!_delta_command) {
793 // Mark all selected notes for selection when model reloads
794 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
795 _marked_for_selection.insert((*i)->note());
798 _model->apply_command(*trackview.session(), _delta_command);
800 midi_view()->midi_track()->playlist_modified();
802 _marked_for_selection.clear();
803 _marked_for_velocity.clear();
807 MidiRegionView::apply_diff ()
809 if (!_diff_command) {
813 _model->apply_command(*trackview.session(), _diff_command);
815 midi_view()->midi_track()->playlist_modified();
817 _marked_for_velocity.clear();
821 MidiRegionView::apply_delta_as_subcommand()
823 if (!_delta_command) {
827 // Mark all selected notes for selection when model reloads
828 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
829 _marked_for_selection.insert((*i)->note());
832 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
834 midi_view()->midi_track()->playlist_modified();
836 _marked_for_selection.clear();
837 _marked_for_velocity.clear();
841 MidiRegionView::apply_diff_as_subcommand()
843 if (!_diff_command) {
847 // Mark all selected notes for selection when model reloads
848 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
849 _marked_for_selection.insert((*i)->note());
852 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
854 midi_view()->midi_track()->playlist_modified();
856 _marked_for_selection.clear();
857 _marked_for_velocity.clear();
861 MidiRegionView::abort_command()
863 delete _delta_command;
865 delete _diff_command;
871 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
873 if (_optimization_iterator != _events.end()) {
874 ++_optimization_iterator;
877 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
878 return *_optimization_iterator;
881 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
882 if ((*_optimization_iterator)->note() == note) {
883 return *_optimization_iterator;
891 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
893 MidiModel::Notes notes;
894 _model->get_notes (notes, op, val, chan_mask);
896 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
897 CanvasNoteEvent* cne = find_canvas_note (*n);
905 MidiRegionView::redisplay_model()
907 // Don't redisplay the model if we're currently recording and displaying that
913 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
917 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
921 MidiModel::ReadLock lock(_model->read_lock());
923 MidiModel::Notes& notes (_model->notes());
924 _optimization_iterator = _events.begin();
926 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
928 boost::shared_ptr<NoteType> note (*n);
929 CanvasNoteEvent* cne;
932 if (note_in_region_range (note, visible)) {
934 if ((cne = find_canvas_note (note)) != 0) {
941 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
943 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
955 add_note (note, visible);
960 if ((cne = find_canvas_note (note)) != 0) {
968 /* remove note items that are no longer valid */
970 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
971 if (!(*i)->valid ()) {
973 i = _events.erase (i);
980 display_program_changes();
982 _marked_for_selection.clear ();
983 _marked_for_velocity.clear ();
985 /* we may have caused _events to contain things out of order (e.g. if a note
986 moved earlier or later). we don't generally need them in time order, but
987 make a note that a sort is required for those cases that require it.
994 MidiRegionView::display_program_changes()
996 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
1001 Glib::Mutex::Lock lock (control->list()->lock());
1003 uint8_t channel = control->parameter().channel();
1005 for (AutomationList::const_iterator event = control->list()->begin();
1006 event != control->list()->end(); ++event) {
1007 double event_time = (*event)->when;
1008 double program_number = floor((*event)->value + 0.5);
1010 // Get current value of bank select MSB at time of the program change
1011 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1012 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1014 if (msb_control != 0) {
1015 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
1018 // Get current value of bank select LSB at time of the program change
1019 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1020 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1022 if (lsb_control != 0) {
1023 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
1026 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1028 boost::shared_ptr<MIDI::Name::Patch> patch =
1029 MIDI::Name::MidiPatchManager::instance().find_patch(
1030 _model_name, _custom_device_mode, channel, patch_key);
1032 PCEvent program_change(event_time, uint8_t(program_number), channel);
1035 add_pgm_change(program_change, patch->name());
1038 snprintf(buf, 4, "%d", int(program_number));
1039 add_pgm_change(program_change, buf);
1045 MidiRegionView::display_sysexes()
1047 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1048 Evoral::MusicalTime time = (*i)->time();
1053 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1054 str << int((*i)->buffer()[b]);
1055 if (b != (*i)->size() -1) {
1059 string text = str.str();
1061 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1063 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1065 double height = midi_stream_view()->contents_height();
1067 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1068 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1070 // Show unless program change is beyond the region bounds
1071 if (time - _region->start() >= _region->length() || time < _region->start()) {
1077 _sys_exes.push_back(sysex);
1082 MidiRegionView::~MidiRegionView ()
1084 in_destructor = true;
1086 note_delete_connection.disconnect ();
1088 delete _list_editor;
1090 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1092 if (_active_notes) {
1099 delete _delta_command;
1103 MidiRegionView::region_resized (const PropertyChange& what_changed)
1105 RegionView::region_resized(what_changed);
1107 if (what_changed.contains (ARDOUR::Properties::position)) {
1108 set_duration(_region->length(), 0);
1109 if (_enable_display) {
1116 MidiRegionView::reset_width_dependent_items (double pixel_width)
1118 RegionView::reset_width_dependent_items(pixel_width);
1119 assert(_pixel_width == pixel_width);
1121 if (_enable_display) {
1127 MidiRegionView::set_height (double height)
1129 static const double FUDGE = 2.0;
1130 const double old_height = _height;
1131 RegionView::set_height(height);
1132 _height = height - FUDGE;
1134 apply_note_range(midi_stream_view()->lowest_note(),
1135 midi_stream_view()->highest_note(),
1136 height != old_height + FUDGE);
1139 name_pixbuf->raise_to_top();
1144 /** Apply the current note range from the stream view
1145 * by repositioning/hiding notes as necessary
1148 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1150 if (!_enable_display) {
1154 if (!force && _current_range_min == min && _current_range_max == max) {
1158 _current_range_min = min;
1159 _current_range_max = max;
1161 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1162 CanvasNoteEvent* event = *i;
1163 boost::shared_ptr<NoteType> note (event->note());
1165 if (note->note() < _current_range_min ||
1166 note->note() > _current_range_max) {
1172 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1174 const double y1 = midi_stream_view()->note_to_y(note->note());
1175 const double y2 = y1 + floor(midi_stream_view()->note_height());
1177 cnote->property_y1() = y1;
1178 cnote->property_y2() = y2;
1180 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1182 double x = trackview.editor().frame_to_pixel(
1183 beats_to_frames(note->time()) - _region->start());
1184 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1185 double y = midi_stream_view()->note_to_y(event->note()->note())
1186 + ((diamond_size-2.0) / 4.0);
1188 chit->set_height (diamond_size);
1189 chit->move (x - chit->x1(), y - chit->y1());
1196 MidiRegionView::add_ghost (TimeAxisView& tv)
1200 double unit_position = _region->position () / samples_per_unit;
1201 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1202 MidiGhostRegion* ghost;
1204 if (mtv && mtv->midi_view()) {
1205 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1206 to allow having midi notes on top of note lines and waveforms.
1208 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1210 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1213 ghost->set_height ();
1214 ghost->set_duration (_region->length() / samples_per_unit);
1215 ghosts.push_back (ghost);
1217 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1218 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1219 ghost->add_note(note);
1223 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1229 /** Begin tracking note state for successive calls to add_event
1232 MidiRegionView::begin_write()
1234 assert(!_active_notes);
1235 _active_notes = new CanvasNote*[128];
1236 for (unsigned i=0; i < 128; ++i) {
1237 _active_notes[i] = 0;
1242 /** Destroy note state for add_event
1245 MidiRegionView::end_write()
1247 delete[] _active_notes;
1249 _marked_for_selection.clear();
1250 _marked_for_velocity.clear();
1254 /** Resolve an active MIDI note (while recording).
1257 MidiRegionView::resolve_note(uint8_t note, double end_time)
1259 if (midi_view()->note_mode() != Sustained) {
1263 if (_active_notes && _active_notes[note]) {
1264 const nframes64_t end_time_frames = beats_to_frames(end_time);
1265 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1266 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1267 _active_notes[note] = 0;
1272 /** Extend active notes to rightmost edge of region (if length is changed)
1275 MidiRegionView::extend_active_notes()
1277 if (!_active_notes) {
1281 for (unsigned i=0; i < 128; ++i) {
1282 if (_active_notes[i]) {
1283 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1289 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1291 if (no_sound_notes || !trackview.editor().sound_notes()) {
1295 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1298 route_ui->midi_track()->write_immediate_event(
1299 note->on_event().size(), note->on_event().buffer());
1301 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1302 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1303 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1304 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1305 note_length_ms, G_PRIORITY_DEFAULT);
1309 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1311 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1314 route_ui->midi_track()->write_immediate_event(
1315 note->off_event().size(), note->off_event().buffer());
1321 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1323 const nframes64_t note_start_frames = beats_to_frames(note->time());
1325 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1326 (note_start_frames < _region->start());
1328 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1329 (note->note() <= midi_stream_view()->highest_note());
1335 MidiRegionView::update_note (CanvasNote* ev)
1337 boost::shared_ptr<NoteType> note = ev->note();
1339 const nframes64_t note_start_frames = beats_to_frames(note->time());
1340 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1342 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1343 const double y1 = midi_stream_view()->note_to_y(note->note());
1344 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1346 ev->property_x1() = x;
1347 ev->property_y1() = y1;
1348 if (note->length() > 0) {
1349 ev->property_x2() = note_endpixel;
1351 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1353 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1355 if (note->length() == 0) {
1356 if (_active_notes) {
1357 assert(note->note() < 128);
1358 // If this note is already active there's a stuck note,
1359 // finish the old note rectangle
1360 if (_active_notes[note->note()]) {
1361 CanvasNote* const old_rect = _active_notes[note->note()];
1362 boost::shared_ptr<NoteType> old_note = old_rect->note();
1363 old_rect->property_x2() = x;
1364 old_rect->property_outline_what() = (guint32) 0xF;
1366 _active_notes[note->note()] = ev;
1368 /* outline all but right edge */
1369 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1371 /* outline all edges */
1372 ev->property_outline_what() = (guint32) 0xF;
1377 MidiRegionView::update_hit (CanvasHit* ev)
1379 boost::shared_ptr<NoteType> note = ev->note();
1381 const nframes64_t note_start_frames = beats_to_frames(note->time());
1382 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1383 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1384 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1389 /** Add a MIDI note to the view (with length).
1391 * If in sustained mode, notes with length 0 will be considered active
1392 * notes, and resolve_note should be called when the corresponding note off
1393 * event arrives, to properly display the note.
1396 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1398 CanvasNoteEvent* event = 0;
1400 assert(note->time() >= 0);
1401 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1403 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1405 if (midi_view()->note_mode() == Sustained) {
1407 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1409 update_note (ev_rect);
1413 MidiGhostRegion* gr;
1415 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1416 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1417 gr->add_note(ev_rect);
1421 } else if (midi_view()->note_mode() == Percussive) {
1423 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1425 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1427 update_hit (ev_diamond);
1436 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1437 note_selected(event, true);
1440 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1441 event->show_velocity();
1443 event->on_channel_selection_change(_last_channel_selection);
1444 _events.push_back(event);
1455 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1456 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1458 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1460 start_delta_command (_("step add"));
1461 delta_add_note (new_note, true, false);
1464 /* potentially extend region to hold new note */
1466 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1467 nframes64_t region_end = _region->position() + _region->length() - 1;
1469 if (end_frame > region_end) {
1470 _region->set_length (end_frame, this);
1477 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1479 assert(program.time >= 0);
1481 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1482 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1484 double height = midi_stream_view()->contents_height();
1486 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1487 new CanvasProgramChange(*this, *group,
1492 _custom_device_mode,
1493 program.time, program.channel, program.value));
1495 // Show unless program change is beyond the region bounds
1496 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1502 _pgm_changes.push_back(pgm_change);
1506 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1508 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1509 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1510 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1512 if (msb_control != 0) {
1513 msb = int(msb_control->get_float(true, time));
1514 cerr << "got msb " << msb;
1517 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1518 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1520 if (lsb_control != 0) {
1521 lsb = lsb_control->get_float(true, time);
1522 cerr << " got lsb " << lsb;
1525 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1526 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1527 float program_number = -1.0;
1528 if (program_control != 0) {
1529 program_number = program_control->get_float(true, time);
1530 cerr << " got program " << program_number << endl;
1533 key.msb = (int) floor(msb + 0.5);
1534 key.lsb = (int) floor(lsb + 0.5);
1535 key.program_number = (int) floor(program_number + 0.5);
1536 assert(key.is_sane());
1541 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1543 // TODO: Get the real event here and alter them at the original times
1544 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1545 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1546 if (msb_control != 0) {
1547 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1550 // TODO: Get the real event here and alter them at the original times
1551 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1552 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1553 if (lsb_control != 0) {
1554 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1557 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1558 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1560 assert(program_control != 0);
1561 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1567 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1569 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1570 alter_program_change(program_change_event, new_patch);
1574 MidiRegionView::previous_program(CanvasProgramChange& program)
1576 MIDI::Name::PatchPrimaryKey key;
1577 get_patch_key_at(program.event_time(), program.channel(), key);
1579 boost::shared_ptr<MIDI::Name::Patch> patch =
1580 MIDI::Name::MidiPatchManager::instance().previous_patch(
1582 _custom_device_mode,
1586 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1588 alter_program_change(program_change_event, patch->patch_primary_key());
1593 MidiRegionView::next_program(CanvasProgramChange& program)
1595 MIDI::Name::PatchPrimaryKey key;
1596 get_patch_key_at(program.event_time(), program.channel(), key);
1598 boost::shared_ptr<MIDI::Name::Patch> patch =
1599 MIDI::Name::MidiPatchManager::instance().next_patch(
1601 _custom_device_mode,
1605 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1607 alter_program_change(program_change_event, patch->patch_primary_key());
1612 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1614 if (_selection.empty()) {
1618 if (_selection.erase (cne) > 0) {
1619 cerr << "Erased a CNE from selection\n";
1624 MidiRegionView::delete_selection()
1626 if (_selection.empty()) {
1630 start_delta_command (_("delete selection"));
1632 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1633 if ((*i)->selected()) {
1634 _delta_command->remove((*i)->note());
1644 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1646 start_delta_command (_("delete note"));
1647 _delta_command->remove (n);
1650 trackview.editor().hide_verbose_canvas_cursor ();
1654 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1656 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1657 if ((*i)->selected() && (*i) != ev) {
1658 (*i)->selected(false);
1659 (*i)->hide_velocity();
1667 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1669 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1672 Selection::iterator tmp = i;
1675 (*i)->selected (false);
1676 _selection.erase (i);
1685 /* don't bother with removing this regionview from the editor selection,
1686 since we're about to add another note, and thus put/keep this
1687 regionview in the editor selection.
1690 if (!ev->selected()) {
1691 add_to_selection (ev);
1696 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1698 uint8_t low_note = 127;
1699 uint8_t high_note = 0;
1700 MidiModel::Notes& notes (_model->notes());
1701 _optimization_iterator = _events.begin();
1707 if (extend && _selection.empty()) {
1713 /* scan existing selection to get note range */
1715 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1716 if ((*i)->note()->note() < low_note) {
1717 low_note = (*i)->note()->note();
1719 if ((*i)->note()->note() > high_note) {
1720 high_note = (*i)->note()->note();
1724 low_note = min (low_note, notenum);
1725 high_note = max (high_note, notenum);
1728 no_sound_notes = true;
1730 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1732 boost::shared_ptr<NoteType> note (*n);
1733 CanvasNoteEvent* cne;
1734 bool select = false;
1736 if (((1 << note->channel()) & channel_mask) != 0) {
1738 if ((note->note() >= low_note && note->note() <= high_note)) {
1741 } else if (note->note() == notenum) {
1747 if ((cne = find_canvas_note (note)) != 0) {
1748 // extend is false because we've taken care of it,
1749 // since it extends by time range, not pitch.
1750 note_selected (cne, add, false);
1754 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1758 no_sound_notes = false;
1762 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1764 MidiModel::Notes& notes (_model->notes());
1765 _optimization_iterator = _events.begin();
1767 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1769 boost::shared_ptr<NoteType> note (*n);
1770 CanvasNoteEvent* cne;
1772 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1773 if ((cne = find_canvas_note (note)) != 0) {
1774 if (cne->selected()) {
1775 note_deselected (cne);
1777 note_selected (cne, true, false);
1785 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1788 clear_selection_except(ev);
1793 if (!ev->selected()) {
1794 add_to_selection (ev);
1798 /* find end of latest note selected, select all between that and the start of "ev" */
1800 Evoral::MusicalTime earliest = DBL_MAX;
1801 Evoral::MusicalTime latest = 0;
1803 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1804 if ((*i)->note()->end_time() > latest) {
1805 latest = (*i)->note()->end_time();
1807 if ((*i)->note()->time() < earliest) {
1808 earliest = (*i)->note()->time();
1812 if (ev->note()->end_time() > latest) {
1813 latest = ev->note()->end_time();
1816 if (ev->note()->time() < earliest) {
1817 earliest = ev->note()->time();
1820 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1822 /* find notes entirely within OR spanning the earliest..latest range */
1824 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1825 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1826 add_to_selection (*i);
1830 /* if events were guaranteed to be time sorted, we could do this.
1831 but as of sept 10th 2009, they no longer are.
1834 if ((*i)->note()->time() > latest) {
1843 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1845 remove_from_selection (ev);
1849 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1859 // TODO: Make this faster by storing the last updated selection rect, and only
1860 // adjusting things that are in the area that appears/disappeared.
1861 // We probably need a tree to be able to find events in O(log(n)) time.
1863 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1865 /* check if any corner of the note is inside the rect
1868 1) this is computing "touched by", not "contained by" the rect.
1869 2) this does not require that events be sorted in time.
1872 const double ix1 = (*i)->x1();
1873 const double ix2 = (*i)->x2();
1874 const double iy1 = (*i)->y1();
1875 const double iy2 = (*i)->y2();
1877 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1878 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1879 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1880 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1883 if (!(*i)->selected()) {
1884 add_to_selection (*i);
1886 } else if ((*i)->selected()) {
1887 // Not inside rectangle
1888 remove_from_selection (*i);
1894 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1896 Selection::iterator i = _selection.find (ev);
1898 if (i != _selection.end()) {
1899 _selection.erase (i);
1902 ev->selected (false);
1903 ev->hide_velocity ();
1905 if (_selection.empty()) {
1906 PublicEditor& editor (trackview.editor());
1907 editor.get_selection().remove (this);
1912 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1914 bool add_mrv_selection = false;
1916 if (_selection.empty()) {
1917 add_mrv_selection = true;
1920 if (_selection.insert (ev).second) {
1921 ev->selected (true);
1922 play_midi_note ((ev)->note());
1925 if (add_mrv_selection) {
1926 PublicEditor& editor (trackview.editor());
1927 editor.get_selection().add (this);
1932 MidiRegionView::move_selection(double dx, double dy)
1934 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1935 (*i)->move_event(dx, dy);
1940 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1942 assert (!_selection.empty());
1944 uint8_t lowest_note_in_selection = 127;
1945 uint8_t highest_note_in_selection = 0;
1946 uint8_t highest_note_difference = 0;
1948 // find highest and lowest notes first
1950 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1951 uint8_t pitch = (*i)->note()->note();
1952 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1953 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1957 cerr << "dnote: " << (int) dnote << endl;
1958 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1959 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1960 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1961 << int(highest_note_in_selection) << endl;
1962 cerr << "selection size: " << _selection.size() << endl;
1963 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1966 // Make sure the note pitch does not exceed the MIDI standard range
1967 if (highest_note_in_selection + dnote > 127) {
1968 highest_note_difference = highest_note_in_selection - 127;
1971 start_diff_command(_("move notes"));
1973 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1975 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1978 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1980 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1983 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1989 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1991 uint8_t original_pitch = (*i)->note()->note();
1992 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1994 // keep notes in standard midi range
1995 clamp_to_0_127(new_pitch);
1997 // keep original pitch if note is dragged outside valid midi range
1998 if ((original_pitch != 0 && new_pitch == 0)
1999 || (original_pitch != 127 && new_pitch == 127)) {
2000 new_pitch = original_pitch;
2003 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2004 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2006 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
2011 // care about notes being moved beyond the upper/lower bounds on the canvas
2012 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2013 highest_note_in_selection > midi_stream_view()->highest_note()) {
2014 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2019 MidiRegionView::snap_pixel_to_frame(double x)
2021 PublicEditor& editor = trackview.editor();
2022 // x is region relative, convert it to global absolute frames
2023 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
2024 editor.snap_to(frame);
2025 return frame - _region->position(); // convert back to region relative
2029 MidiRegionView::snap_frame_to_frame(nframes64_t x)
2031 PublicEditor& editor = trackview.editor();
2032 // x is region relative, convert it to global absolute frames
2033 nframes64_t frame = x + _region->position();
2034 editor.snap_to(frame);
2035 return frame - _region->position(); // convert back to region relative
2039 MidiRegionView::snap_to_pixel(double x)
2041 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2045 MidiRegionView::get_position_pixels()
2047 nframes64_t region_frame = get_position();
2048 return trackview.editor().frame_to_pixel(region_frame);
2052 MidiRegionView::get_end_position_pixels()
2054 nframes64_t frame = get_position() + get_duration ();
2055 return trackview.editor().frame_to_pixel(frame);
2059 MidiRegionView::beats_to_frames(double beats) const
2061 return _time_converter.to(beats);
2065 MidiRegionView::frames_to_beats(nframes64_t frames) const
2067 return _time_converter.from(frames);
2071 MidiRegionView::begin_resizing (bool /*at_front*/)
2073 _resize_data.clear();
2075 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2076 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2078 // only insert CanvasNotes into the map
2080 NoteResizeData *resize_data = new NoteResizeData();
2081 resize_data->canvas_note = note;
2083 // create a new SimpleRect from the note which will be the resize preview
2084 SimpleRect *resize_rect = new SimpleRect(
2085 *group, note->x1(), note->y1(), note->x2(), note->y2());
2087 // calculate the colors: get the color settings
2088 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2089 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2092 // make the resize preview notes more transparent and bright
2093 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2095 // calculate color based on note velocity
2096 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2097 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
2101 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2102 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2104 resize_data->resize_rect = resize_rect;
2105 _resize_data.push_back(resize_data);
2110 /** Update resizing notes while user drags.
2111 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2112 * @param at_front which end of the note (true == note on, false == note off)
2113 * @param delta_x change in mouse position since the start of the drag
2114 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2115 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2116 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2117 * as the \a primary note.
2120 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2122 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2123 SimpleRect* resize_rect = (*i)->resize_rect;
2124 CanvasNote* canvas_note = (*i)->canvas_note;
2129 current_x = canvas_note->x1() + delta_x;
2131 current_x = primary->x1() + delta_x;
2135 current_x = canvas_note->x2() + delta_x;
2137 current_x = primary->x2() + delta_x;
2142 resize_rect->property_x1() = snap_to_pixel(current_x);
2143 resize_rect->property_x2() = canvas_note->x2();
2145 resize_rect->property_x2() = snap_to_pixel(current_x);
2146 resize_rect->property_x1() = canvas_note->x1();
2152 /** Finish resizing notes when the user releases the mouse button.
2153 * Parameters the same as for \a update_resizing().
2156 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2158 start_diff_command(_("resize notes"));
2160 CanvasNote* first = _resize_data.empty() ? 0 : _resize_data.front()->canvas_note;
2162 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2163 CanvasNote* canvas_note = (*i)->canvas_note;
2164 SimpleRect* resize_rect = (*i)->resize_rect;
2169 current_x = canvas_note->x1() + delta_x;
2171 current_x = primary->x1() + delta_x;
2175 current_x = canvas_note->x2() + delta_x;
2177 current_x = primary->x2() + delta_x;
2181 current_x = snap_pixel_to_frame (current_x);
2182 current_x = frames_to_beats (current_x);
2184 if (at_front && current_x < canvas_note->note()->end_time()) {
2185 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2187 double len = canvas_note->note()->time() - current_x;
2188 len += canvas_note->note()->length();
2191 /* XXX convert to beats */
2192 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2197 double len = current_x - canvas_note->note()->time();
2200 /* XXX convert to beats */
2201 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2209 _resize_data.clear();
2214 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2216 uint8_t new_velocity;
2219 new_velocity = event->note()->velocity() + velocity;
2220 clamp_to_0_127(new_velocity);
2222 new_velocity = velocity;
2225 event->show_velocity ();
2227 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2231 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2236 new_note = event->note()->note() + note;
2241 clamp_to_0_127 (new_note);
2242 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2246 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2248 bool change_start = false;
2249 bool change_length = false;
2250 Evoral::MusicalTime new_start;
2251 Evoral::MusicalTime new_length;
2253 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2255 front_delta: if positive - move the start of the note later in time (shortening it)
2256 if negative - move the start of the note earlier in time (lengthening it)
2258 end_delta: if positive - move the end of the note later in time (lengthening it)
2259 if negative - move the end of the note earlier in time (shortening it)
2263 if (front_delta < 0) {
2265 if (event->note()->time() < -front_delta) {
2268 new_start = event->note()->time() + front_delta; // moves earlier
2271 /* start moved toward zero, so move the end point out to where it used to be.
2272 Note that front_delta is negative, so this increases the length.
2275 new_length = event->note()->length() - front_delta;
2276 change_start = true;
2277 change_length = true;
2281 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2283 if (new_pos < event->note()->end_time()) {
2284 new_start = event->note()->time() + front_delta;
2285 /* start moved toward the end, so move the end point back to where it used to be */
2286 new_length = event->note()->length() - front_delta;
2287 change_start = true;
2288 change_length = true;
2295 bool can_change = true;
2296 if (end_delta < 0) {
2297 if (event->note()->length() < -end_delta) {
2303 new_length = event->note()->length() + end_delta;
2304 change_length = true;
2309 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2312 if (change_length) {
2313 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2318 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2320 Evoral::MusicalTime new_time;
2324 if (event->note()->time() < -delta) {
2327 new_time = event->note()->time() + delta;
2330 new_time = event->note()->time() + delta;
2336 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2340 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2344 if (_selection.empty()) {
2359 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2360 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2366 start_diff_command(_("change velocities"));
2368 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2369 Selection::iterator next = i;
2371 change_note_velocity (*i, delta, true);
2380 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2382 if (_selection.empty()) {
2399 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2401 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2405 if ((int8_t) (*i)->note()->note() + delta > 127) {
2412 start_diff_command (_("transpose"));
2414 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2415 Selection::iterator next = i;
2417 change_note_note (*i, delta, true);
2425 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2427 Evoral::MusicalTime delta;
2432 /* grab the current grid distance */
2434 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2436 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2437 cerr << "Grid type not available as beats - TO BE FIXED\n";
2446 start_diff_command (_("change note lengths"));
2448 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2449 Selection::iterator next = i;
2452 /* note the negation of the delta for start */
2454 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2463 MidiRegionView::nudge_notes (bool forward)
2465 if (_selection.empty()) {
2469 /* pick a note as the point along the timeline to get the nudge distance.
2470 its not necessarily the earliest note, so we may want to pull the notes out
2471 into a vector and sort before using the first one.
2474 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2476 nframes64_t distance;
2478 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2480 /* grid is off - use nudge distance */
2482 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2488 nframes64_t next_pos = ref_point;
2491 /* XXX need check on max_frames, but that needs max_frames64 or something */
2494 if (next_pos == 0) {
2500 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2501 distance = ref_point - next_pos;
2504 if (distance == 0) {
2508 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2514 start_diff_command (_("nudge"));
2516 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2517 Selection::iterator next = i;
2519 change_note_time (*i, delta, true);
2527 MidiRegionView::change_channel(uint8_t channel)
2529 start_diff_command(_("change channel"));
2530 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2531 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2539 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2541 if (_mouse_state == SelectTouchDragging) {
2542 note_selected (ev, true);
2545 show_verbose_canvas_cursor (ev->note ());
2549 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2551 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2552 (*i)->hide_velocity ();
2555 trackview.editor().hide_verbose_canvas_cursor ();
2559 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2561 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2563 display_model(msrc->model());
2567 MidiRegionView::set_frame_color()
2570 if (_selected && should_show_selection) {
2571 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2573 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2579 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2583 case FilterChannels:
2584 _force_channel = -1;
2587 _force_channel = mask;
2588 mask = 0xFFFF; // Show all notes as active (below)
2591 // Update notes for selection
2592 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2593 (*i)->on_channel_selection_change(mask);
2596 _last_channel_selection = mask;
2600 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2602 _model_name = model;
2603 _custom_device_mode = custom_device_mode;
2608 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2610 if (_selection.empty()) {
2614 PublicEditor& editor (trackview.editor());
2619 editor.get_cut_buffer().add (selection_as_cut_buffer());
2627 start_delta_command();
2629 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2635 delta_remove_note (*i);
2645 MidiRegionView::selection_as_cut_buffer () const
2649 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2650 NoteType* n = (*i)->note().get();
2651 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2654 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2661 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2667 start_delta_command (_("paste"));
2669 Evoral::MusicalTime beat_delta;
2670 Evoral::MusicalTime paste_pos_beats;
2671 Evoral::MusicalTime duration;
2672 Evoral::MusicalTime end_point;
2674 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2675 paste_pos_beats = frames_to_beats (pos - _region->position());
2676 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2677 paste_pos_beats = 0;
2679 _selection.clear ();
2681 for (int n = 0; n < (int) times; ++n) {
2683 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2685 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2687 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2688 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2690 /* make all newly added notes selected */
2692 delta_add_note (copied_note, true);
2693 end_point = copied_note->end_time();
2696 paste_pos_beats += duration;
2699 /* if we pasted past the current end of the region, extend the region */
2701 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2702 nframes64_t region_end = _region->position() + _region->length() - 1;
2704 if (end_frame > region_end) {
2706 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2708 trackview.session()->begin_reversible_command (_("paste"));
2710 _region->clear_history ();
2711 _region->set_length (end_frame, this);
2712 trackview.session()->add_command (new StatefulDiffCommand (_region));
2715 cerr << "region end finally at " << _region->position() + _region->length() - 1;
2719 struct EventNoteTimeEarlyFirstComparator {
2720 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2721 return a->note()->time() < b->note()->time();
2726 MidiRegionView::time_sort_events ()
2728 if (!_sort_needed) {
2732 EventNoteTimeEarlyFirstComparator cmp;
2735 _sort_needed = false;
2739 MidiRegionView::goto_next_note ()
2741 // nframes64_t pos = -1;
2742 bool use_next = false;
2744 if (_events.back()->selected()) {
2748 time_sort_events ();
2750 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2751 if ((*i)->selected()) {
2754 } else if (use_next) {
2756 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2761 /* use the first one */
2763 unique_select (_events.front());
2768 MidiRegionView::goto_previous_note ()
2770 // nframes64_t pos = -1;
2771 bool use_next = false;
2773 if (_events.front()->selected()) {
2777 time_sort_events ();
2779 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2780 if ((*i)->selected()) {
2783 } else if (use_next) {
2785 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2790 /* use the last one */
2792 unique_select (*(_events.rbegin()));
2796 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2798 bool had_selected = false;
2800 time_sort_events ();
2802 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2803 if ((*i)->selected()) {
2804 selected.insert ((*i)->note());
2805 had_selected = true;
2809 if (allow_all_if_none_selected && !had_selected) {
2810 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2811 selected.insert ((*i)->note());
2817 MidiRegionView::update_ghost_note (double x, double y)
2823 nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2824 trackview.editor().snap_to (f);
2825 f -= _region->position ();
2828 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2833 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2835 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2836 _ghost_note->note()->set_length (length);
2837 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2839 update_note (_ghost_note);
2841 show_verbose_canvas_cursor (_ghost_note->note ());
2845 MidiRegionView::create_ghost_note (double x, double y)
2850 boost::shared_ptr<NoteType> g (new NoteType);
2851 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2852 update_ghost_note (x, y);
2853 _ghost_note->show ();
2858 show_verbose_canvas_cursor (_ghost_note->note ());
2862 MidiRegionView::snap_changed ()
2868 create_ghost_note (_last_ghost_x, _last_ghost_y);
2872 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2875 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (n->note()).c_str(), (int) n->note ());
2876 trackview.editor().show_verbose_canvas_cursor_with (buf);
2880 MidiRegionView::drop_down_keys ()
2882 _mouse_state = None;
2886 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
2888 double note = midi_stream_view()->y_to_note(y);
2890 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2892 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
2894 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
2895 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
2896 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
2897 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
2902 bool add_mrv_selection = false;
2904 if (_selection.empty()) {
2905 add_mrv_selection = true;
2908 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
2909 if (_selection.insert (*i).second) {
2910 (*i)->selected (true);
2914 if (add_mrv_selection) {
2915 PublicEditor& editor (trackview.editor());
2916 editor.get_selection().add (this);