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))
92 , _optimization_iterator (_events.end())
94 , no_sound_notes (false)
96 _note_group->raise_to_top();
97 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
100 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
101 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
102 TimeAxisViewItem::Visibility visibility)
103 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
105 , _last_channel_selection(0xFFFF)
106 , _model_name(string())
107 , _custom_device_mode(string())
109 , _note_group(new ArdourCanvas::Group(*parent))
115 , _sort_needed (true)
116 , _optimization_iterator (_events.end())
118 , no_sound_notes (false)
120 _note_group->raise_to_top();
121 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
124 MidiRegionView::MidiRegionView (const MidiRegionView& other)
125 : sigc::trackable(other)
128 , _last_channel_selection(0xFFFF)
129 , _model_name(string())
130 , _custom_device_mode(string())
132 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
138 , _sort_needed (true)
139 , _optimization_iterator (_events.end())
141 , no_sound_notes (false)
146 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
147 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
152 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
153 : RegionView (other, boost::shared_ptr<Region> (region))
155 , _last_channel_selection(0xFFFF)
156 , _model_name(string())
157 , _custom_device_mode(string())
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
165 , _sort_needed (true)
166 , _optimization_iterator (_events.end())
168 , no_sound_notes (false)
173 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
174 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
180 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
182 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
184 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
185 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
189 midi_region()->midi_source(0)->load_model();
192 _model = midi_region()->midi_source(0)->model();
193 _enable_display = false;
195 RegionView::init (basic_color, false);
197 compute_colors (basic_color);
199 set_height (trackview.current_height());
202 region_sync_changed ();
203 region_resized (ARDOUR::bounds_change);
206 reset_width_dependent_items (_pixel_width);
210 _enable_display = true;
213 display_model (_model);
217 group->raise_to_top();
218 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
220 midi_view()->signal_channel_mode_changed().connect(
221 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
223 midi_view()->signal_midi_patch_settings_changed().connect(
224 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
226 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
230 MidiRegionView::canvas_event(GdkEvent* ev)
232 if (!trackview.editor().internal_editing()) {
236 /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
237 to its items, which means that ev->type == GDK_SCROLL will never be seen
242 return scroll (&ev->scroll);
245 return key_press (&ev->key);
247 case GDK_KEY_RELEASE:
248 return key_release (&ev->key);
250 case GDK_BUTTON_PRESS:
251 return button_press (&ev->button);
253 case GDK_2BUTTON_PRESS:
256 case GDK_BUTTON_RELEASE:
257 return button_release (&ev->button);
259 case GDK_ENTER_NOTIFY:
260 return enter_notify (&ev->crossing);
262 case GDK_LEAVE_NOTIFY:
263 return leave_notify (&ev->crossing);
265 case GDK_MOTION_NOTIFY:
266 return motion (&ev->motion);
276 MidiRegionView::enter_notify (GdkEventCrossing* ev)
278 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
280 Keyboard::magic_widget_grab_focus();
283 if (trackview.editor().current_mouse_mode() == MouseRange) {
284 create_ghost_note (ev->x, ev->y);
291 MidiRegionView::leave_notify (GdkEventCrossing* ev)
293 trackview.editor().hide_verbose_canvas_cursor ();
300 MidiRegionView::button_press (GdkEventButton* ev)
304 group->w2i (_last_x, _last_y);
306 if (_mouse_state != SelectTouchDragging && ev->button == 1) {
307 _pressed_button = ev->button;
308 _mouse_state = Pressed;
311 _pressed_button = ev->button;
317 MidiRegionView::button_release (GdkEventButton* ev)
319 double event_x, event_y;
320 nframes64_t event_frame = 0;
324 group->w2i(event_x, event_y);
325 group->ungrab(ev->time);
326 event_frame = trackview.editor().pixel_to_frame(event_x);
328 if (ev->button == 3) {
330 } else if (_pressed_button != 1) {
334 switch (_mouse_state) {
335 case Pressed: // Clicked
336 switch (trackview.editor().current_mouse_mode()) {
340 maybe_select_by_position (ev, event_x, event_y);
346 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
351 create_note_at (event_x, event_y, beats);
359 case SelectRectDragging: // Select drag done
365 case AddDragging: // Add drag done
367 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
368 const double x = _drag_rect->property_x1();
369 const double length = trackview.editor().pixel_to_frame
370 (_drag_rect->property_x2() - _drag_rect->property_x1());
372 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length));
378 create_ghost_note (ev->x, ev->y);
388 MidiRegionView::motion (GdkEventMotion* ev)
390 double event_x, event_y;
391 nframes64_t event_frame = 0;
395 group->w2i(event_x, event_y);
397 // convert event_x to global frame
398 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
399 trackview.editor().snap_to(event_frame);
400 // convert event_frame back to local coordinates relative to position
401 event_frame -= _region->position();
404 update_ghost_note (ev->x, ev->y);
407 /* any motion immediately hides velocity text that may have been visible */
409 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
410 (*i)->hide_velocity ();
413 switch (_mouse_state) {
414 case Pressed: // Maybe start a drag, if we've moved a bit
416 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
417 /* no appreciable movement since the button was pressed */
422 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
423 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
424 Gdk::Cursor(Gdk::FLEUR), ev->time);
427 _drag_start_x = event_x;
428 _drag_start_y = event_y;
430 _drag_rect = new ArdourCanvas::SimpleRect(*group);
431 _drag_rect->property_x1() = event_x;
432 _drag_rect->property_y1() = event_y;
433 _drag_rect->property_x2() = event_x;
434 _drag_rect->property_y2() = event_y;
435 _drag_rect->property_outline_what() = 0xFF;
436 _drag_rect->property_outline_color_rgba()
437 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
438 _drag_rect->property_fill_color_rgba()
439 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
441 _mouse_state = SelectRectDragging;
444 // Add note drag start
445 } else if (trackview.editor().internal_editing()) {
450 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
451 Gdk::Cursor(Gdk::FLEUR), ev->time);
454 _drag_start_x = event_x;
455 _drag_start_y = event_y;
457 _drag_rect = new ArdourCanvas::SimpleRect(*group);
458 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
460 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
461 midi_stream_view()->y_to_note(event_y));
462 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
463 _drag_rect->property_y2() = _drag_rect->property_y1()
464 + floor(midi_stream_view()->note_height());
465 _drag_rect->property_outline_what() = 0xFF;
466 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
467 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
469 _mouse_state = AddDragging;
475 case SelectRectDragging: // Select drag motion
476 case AddDragging: // Add note drag motion
480 GdkModifierType state;
481 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
486 if (_mouse_state == AddDragging)
487 event_x = trackview.editor().frame_to_pixel(event_frame);
490 if (event_x > _drag_start_x)
491 _drag_rect->property_x2() = event_x;
493 _drag_rect->property_x1() = event_x;
496 if (_drag_rect && _mouse_state == SelectRectDragging) {
497 if (event_y > _drag_start_y)
498 _drag_rect->property_y2() = event_y;
500 _drag_rect->property_y1() = event_y;
502 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
508 case SelectTouchDragging:
520 MidiRegionView::scroll (GdkEventScroll* ev)
522 if (_selection.empty()) {
526 trackview.editor().hide_verbose_canvas_cursor ();
528 bool fine = Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
530 if (ev->direction == GDK_SCROLL_UP) {
531 change_velocities (true, fine, false);
532 } else if (ev->direction == GDK_SCROLL_DOWN) {
533 change_velocities (false, fine, false);
539 MidiRegionView::key_press (GdkEventKey* ev)
541 /* since GTK bindings are generally activated on press, and since
542 detectable auto-repeat is the name of the game and only sends
543 repeated presses, carry out key actions at key press, not release.
546 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
547 _mouse_state = SelectTouchDragging;
550 } else if (ev->keyval == GDK_Escape) {
554 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
556 bool start = (ev->keyval == GDK_comma);
557 bool end = (ev->keyval == GDK_period);
558 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
559 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
561 change_note_lengths (fine, shorter, start, end);
565 } else if (ev->keyval == GDK_Delete) {
570 } else if (ev->keyval == GDK_Tab) {
572 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
573 goto_previous_note ();
579 } else if (ev->keyval == GDK_Up) {
581 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
582 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
584 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
585 change_velocities (true, fine, allow_smush);
587 transpose (true, fine, allow_smush);
591 } else if (ev->keyval == GDK_Down) {
593 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
594 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
596 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
597 change_velocities (false, fine, allow_smush);
599 transpose (false, fine, allow_smush);
603 } else if (ev->keyval == GDK_Left) {
608 } else if (ev->keyval == GDK_Right) {
613 } else if (ev->keyval == GDK_Control_L) {
616 } else if (ev->keyval == GDK_r) {
617 /* if we're not step editing, this really doesn't matter */
618 midi_view()->step_edit_rest ();
626 MidiRegionView::key_release (GdkEventKey* ev)
628 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
636 MidiRegionView::show_list_editor ()
639 _list_editor = new MidiListEditor (trackview.session(), midi_region());
641 _list_editor->present ();
644 /** Add a note to the model, and the view, at a canvas (click) coordinate.
645 * \param x horizontal position in pixels
646 * \param y vertical position in pixels
647 * \param length duration of the note in beats */
649 MidiRegionView::create_note_at(double x, double y, double length)
651 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
652 MidiStreamView* const view = mtv->midi_view();
654 double note = midi_stream_view()->y_to_note(y);
657 assert(note <= 127.0);
659 // Start of note in frames relative to region start
660 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
661 assert(start_frames >= 0);
664 length = frames_to_beats(
665 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
667 assert (length != 0);
669 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
670 frames_to_beats(start_frames + _region->start()), length,
671 (uint8_t)note, 0x40));
673 if (_model->contains (new_note)) {
677 view->update_note_range(new_note->note());
679 MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
681 _model->apply_command(*trackview.session(), cmd);
683 play_midi_note (new_note);
687 MidiRegionView::clear_events()
692 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
693 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
698 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
703 _pgm_changes.clear();
705 _optimization_iterator = _events.end();
710 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
713 content_connection.disconnect ();
714 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
718 if (_enable_display) {
724 MidiRegionView::start_diff_command(string name)
726 if (!_diff_command) {
727 _diff_command = _model->new_diff_command(name);
732 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
735 _diff_command->add(note);
738 _marked_for_selection.insert(note);
741 _marked_for_velocity.insert(note);
746 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
748 if (_diff_command && ev->note()) {
749 _diff_command->remove(ev->note());
754 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
755 MidiModel::DiffCommand::Property property,
759 _diff_command->change (ev->note(), property, val);
764 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
765 MidiModel::DiffCommand::Property property,
766 Evoral::MusicalTime val)
769 _diff_command->change (ev->note(), property, val);
774 MidiRegionView::apply_diff ()
778 if (!_diff_command) {
782 if ((add_or_remove = _diff_command->adds_or_removes())) {
783 // Mark all selected notes for selection when model reloads
784 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
785 _marked_for_selection.insert((*i)->note());
789 _model->apply_command(*trackview.session(), _diff_command);
791 midi_view()->midi_track()->playlist_modified();
795 _marked_for_selection.clear();
798 _marked_for_velocity.clear();
802 MidiRegionView::apply_diff_as_subcommand()
806 if (!_diff_command) {
810 if ((add_or_remove = _diff_command->adds_or_removes())) {
811 // Mark all selected notes for selection when model reloads
812 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
813 _marked_for_selection.insert((*i)->note());
817 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
819 midi_view()->midi_track()->playlist_modified();
822 _marked_for_selection.clear();
824 _marked_for_velocity.clear();
829 MidiRegionView::abort_command()
831 delete _diff_command;
837 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
839 if (_optimization_iterator != _events.end()) {
840 ++_optimization_iterator;
843 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
844 return *_optimization_iterator;
847 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
848 if ((*_optimization_iterator)->note() == note) {
849 return *_optimization_iterator;
857 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
859 MidiModel::Notes notes;
860 _model->get_notes (notes, op, val, chan_mask);
862 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
863 CanvasNoteEvent* cne = find_canvas_note (*n);
871 MidiRegionView::redisplay_model()
873 // Don't redisplay the model if we're currently recording and displaying that
879 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
883 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
887 MidiModel::ReadLock lock(_model->read_lock());
889 MidiModel::Notes& notes (_model->notes());
890 _optimization_iterator = _events.begin();
892 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
894 boost::shared_ptr<NoteType> note (*n);
895 CanvasNoteEvent* cne;
898 if (note_in_region_range (note, visible)) {
900 if ((cne = find_canvas_note (note)) != 0) {
907 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
909 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
921 add_note (note, visible);
926 if ((cne = find_canvas_note (note)) != 0) {
934 /* remove note items that are no longer valid */
936 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
937 if (!(*i)->valid ()) {
939 i = _events.erase (i);
946 display_program_changes();
948 _marked_for_selection.clear ();
949 _marked_for_velocity.clear ();
951 /* we may have caused _events to contain things out of order (e.g. if a note
952 moved earlier or later). we don't generally need them in time order, but
953 make a note that a sort is required for those cases that require it.
960 MidiRegionView::display_program_changes()
962 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
967 Glib::Mutex::Lock lock (control->list()->lock());
969 uint8_t channel = control->parameter().channel();
971 for (AutomationList::const_iterator event = control->list()->begin();
972 event != control->list()->end(); ++event) {
973 double event_time = (*event)->when;
974 double program_number = floor((*event)->value + 0.5);
976 // Get current value of bank select MSB at time of the program change
977 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
978 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
980 if (msb_control != 0) {
981 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
984 // Get current value of bank select LSB at time of the program change
985 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
986 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
988 if (lsb_control != 0) {
989 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
992 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
994 boost::shared_ptr<MIDI::Name::Patch> patch =
995 MIDI::Name::MidiPatchManager::instance().find_patch(
996 _model_name, _custom_device_mode, channel, patch_key);
998 PCEvent program_change(event_time, uint8_t(program_number), channel);
1001 add_pgm_change(program_change, patch->name());
1004 snprintf(buf, 4, "%d", int(program_number));
1005 add_pgm_change(program_change, buf);
1011 MidiRegionView::display_sysexes()
1013 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1014 Evoral::MusicalTime time = (*i)->time();
1019 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1020 str << int((*i)->buffer()[b]);
1021 if (b != (*i)->size() -1) {
1025 string text = str.str();
1027 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1029 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1031 double height = midi_stream_view()->contents_height();
1033 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1034 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1036 // Show unless program change is beyond the region bounds
1037 if (time - _region->start() >= _region->length() || time < _region->start()) {
1043 _sys_exes.push_back(sysex);
1048 MidiRegionView::~MidiRegionView ()
1050 in_destructor = true;
1052 note_delete_connection.disconnect ();
1054 delete _list_editor;
1056 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1058 if (_active_notes) {
1065 delete _diff_command;
1069 MidiRegionView::region_resized (const PropertyChange& what_changed)
1071 RegionView::region_resized(what_changed);
1073 if (what_changed.contains (ARDOUR::Properties::position)) {
1074 set_duration(_region->length(), 0);
1075 if (_enable_display) {
1082 MidiRegionView::reset_width_dependent_items (double pixel_width)
1084 RegionView::reset_width_dependent_items(pixel_width);
1085 assert(_pixel_width == pixel_width);
1087 if (_enable_display) {
1093 MidiRegionView::set_height (double height)
1095 static const double FUDGE = 2.0;
1096 const double old_height = _height;
1097 RegionView::set_height(height);
1098 _height = height - FUDGE;
1100 apply_note_range(midi_stream_view()->lowest_note(),
1101 midi_stream_view()->highest_note(),
1102 height != old_height + FUDGE);
1105 name_pixbuf->raise_to_top();
1110 /** Apply the current note range from the stream view
1111 * by repositioning/hiding notes as necessary
1114 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1116 if (!_enable_display) {
1120 if (!force && _current_range_min == min && _current_range_max == max) {
1124 _current_range_min = min;
1125 _current_range_max = max;
1127 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1128 CanvasNoteEvent* event = *i;
1129 boost::shared_ptr<NoteType> note (event->note());
1131 if (note->note() < _current_range_min ||
1132 note->note() > _current_range_max) {
1138 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1140 const double y1 = midi_stream_view()->note_to_y(note->note());
1141 const double y2 = y1 + floor(midi_stream_view()->note_height());
1143 cnote->property_y1() = y1;
1144 cnote->property_y2() = y2;
1146 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1148 double x = trackview.editor().frame_to_pixel(
1149 beats_to_frames(note->time()) - _region->start());
1150 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1151 double y = midi_stream_view()->note_to_y(event->note()->note())
1152 + ((diamond_size-2.0) / 4.0);
1154 chit->set_height (diamond_size);
1155 chit->move (x - chit->x1(), y - chit->y1());
1162 MidiRegionView::add_ghost (TimeAxisView& tv)
1166 double unit_position = _region->position () / samples_per_unit;
1167 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1168 MidiGhostRegion* ghost;
1170 if (mtv && mtv->midi_view()) {
1171 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1172 to allow having midi notes on top of note lines and waveforms.
1174 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1176 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1179 ghost->set_height ();
1180 ghost->set_duration (_region->length() / samples_per_unit);
1181 ghosts.push_back (ghost);
1183 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1184 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1185 ghost->add_note(note);
1189 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1195 /** Begin tracking note state for successive calls to add_event
1198 MidiRegionView::begin_write()
1200 assert(!_active_notes);
1201 _active_notes = new CanvasNote*[128];
1202 for (unsigned i=0; i < 128; ++i) {
1203 _active_notes[i] = 0;
1208 /** Destroy note state for add_event
1211 MidiRegionView::end_write()
1213 delete[] _active_notes;
1215 _marked_for_selection.clear();
1216 _marked_for_velocity.clear();
1220 /** Resolve an active MIDI note (while recording).
1223 MidiRegionView::resolve_note(uint8_t note, double end_time)
1225 if (midi_view()->note_mode() != Sustained) {
1229 if (_active_notes && _active_notes[note]) {
1230 const nframes64_t end_time_frames = beats_to_frames(end_time);
1231 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1232 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1233 _active_notes[note] = 0;
1238 /** Extend active notes to rightmost edge of region (if length is changed)
1241 MidiRegionView::extend_active_notes()
1243 if (!_active_notes) {
1247 for (unsigned i=0; i < 128; ++i) {
1248 if (_active_notes[i]) {
1249 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1255 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1257 if (no_sound_notes || !trackview.editor().sound_notes()) {
1261 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1264 route_ui->midi_track()->write_immediate_event(
1265 note->on_event().size(), note->on_event().buffer());
1267 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1268 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1269 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1270 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1271 note_length_ms, G_PRIORITY_DEFAULT);
1275 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1277 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1280 route_ui->midi_track()->write_immediate_event(
1281 note->off_event().size(), note->off_event().buffer());
1287 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1289 const nframes64_t note_start_frames = beats_to_frames(note->time());
1291 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1292 (note_start_frames < _region->start());
1294 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1295 (note->note() <= midi_stream_view()->highest_note());
1301 MidiRegionView::update_note (CanvasNote* ev)
1303 boost::shared_ptr<NoteType> note = ev->note();
1305 const nframes64_t note_start_frames = beats_to_frames(note->time());
1306 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1308 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1309 const double y1 = midi_stream_view()->note_to_y(note->note());
1310 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1312 ev->property_x1() = x;
1313 ev->property_y1() = y1;
1314 if (note->length() > 0) {
1315 ev->property_x2() = note_endpixel;
1317 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1319 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1321 if (note->length() == 0) {
1322 if (_active_notes) {
1323 assert(note->note() < 128);
1324 // If this note is already active there's a stuck note,
1325 // finish the old note rectangle
1326 if (_active_notes[note->note()]) {
1327 CanvasNote* const old_rect = _active_notes[note->note()];
1328 boost::shared_ptr<NoteType> old_note = old_rect->note();
1329 old_rect->property_x2() = x;
1330 old_rect->property_outline_what() = (guint32) 0xF;
1332 _active_notes[note->note()] = ev;
1334 /* outline all but right edge */
1335 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1337 /* outline all edges */
1338 ev->property_outline_what() = (guint32) 0xF;
1343 MidiRegionView::update_hit (CanvasHit* ev)
1345 boost::shared_ptr<NoteType> note = ev->note();
1347 const nframes64_t note_start_frames = beats_to_frames(note->time());
1348 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1349 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1350 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1355 /** Add a MIDI note to the view (with length).
1357 * If in sustained mode, notes with length 0 will be considered active
1358 * notes, and resolve_note should be called when the corresponding note off
1359 * event arrives, to properly display the note.
1362 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1364 CanvasNoteEvent* event = 0;
1366 assert(note->time() >= 0);
1367 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1369 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1371 if (midi_view()->note_mode() == Sustained) {
1373 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1375 update_note (ev_rect);
1379 MidiGhostRegion* gr;
1381 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1382 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1383 gr->add_note(ev_rect);
1387 } else if (midi_view()->note_mode() == Percussive) {
1389 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1391 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1393 update_hit (ev_diamond);
1402 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1403 note_selected(event, true);
1406 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1407 event->show_velocity();
1409 event->on_channel_selection_change(_last_channel_selection);
1410 _events.push_back(event);
1421 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1422 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1424 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1426 start_diff_command (_("step add"));
1427 diff_add_note (new_note, true, false);
1430 /* potentially extend region to hold new note */
1432 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1433 nframes64_t region_end = _region->position() + _region->length() - 1;
1435 if (end_frame > region_end) {
1436 _region->set_length (end_frame, this);
1443 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1445 assert(program.time >= 0);
1447 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1448 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1450 double height = midi_stream_view()->contents_height();
1452 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1453 new CanvasProgramChange(*this, *group,
1458 _custom_device_mode,
1459 program.time, program.channel, program.value));
1461 // Show unless program change is beyond the region bounds
1462 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1468 _pgm_changes.push_back(pgm_change);
1472 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1474 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1475 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1476 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1478 if (msb_control != 0) {
1479 msb = int(msb_control->get_float(true, time));
1480 cerr << "got msb " << msb;
1483 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1484 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1486 if (lsb_control != 0) {
1487 lsb = lsb_control->get_float(true, time);
1488 cerr << " got lsb " << lsb;
1491 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1492 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1493 float program_number = -1.0;
1494 if (program_control != 0) {
1495 program_number = program_control->get_float(true, time);
1496 cerr << " got program " << program_number << endl;
1499 key.msb = (int) floor(msb + 0.5);
1500 key.lsb = (int) floor(lsb + 0.5);
1501 key.program_number = (int) floor(program_number + 0.5);
1502 assert(key.is_sane());
1507 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1509 // TODO: Get the real event here and alter them at the original times
1510 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1511 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1512 if (msb_control != 0) {
1513 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1516 // TODO: Get the real event here and alter them at the original times
1517 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1518 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1519 if (lsb_control != 0) {
1520 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1523 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1524 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1526 assert(program_control != 0);
1527 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1533 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1535 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1536 alter_program_change(program_change_event, new_patch);
1540 MidiRegionView::previous_program(CanvasProgramChange& program)
1542 MIDI::Name::PatchPrimaryKey key;
1543 get_patch_key_at(program.event_time(), program.channel(), key);
1545 boost::shared_ptr<MIDI::Name::Patch> patch =
1546 MIDI::Name::MidiPatchManager::instance().previous_patch(
1548 _custom_device_mode,
1552 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1554 alter_program_change(program_change_event, patch->patch_primary_key());
1559 MidiRegionView::next_program(CanvasProgramChange& program)
1561 MIDI::Name::PatchPrimaryKey key;
1562 get_patch_key_at(program.event_time(), program.channel(), key);
1564 boost::shared_ptr<MIDI::Name::Patch> patch =
1565 MIDI::Name::MidiPatchManager::instance().next_patch(
1567 _custom_device_mode,
1571 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1573 alter_program_change(program_change_event, patch->patch_primary_key());
1578 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1580 if (_selection.empty()) {
1584 if (_selection.erase (cne) > 0) {
1585 cerr << "Erased a CNE from selection\n";
1590 MidiRegionView::delete_selection()
1592 if (_selection.empty()) {
1596 start_diff_command (_("delete selection"));
1598 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1599 if ((*i)->selected()) {
1600 _diff_command->remove((*i)->note());
1610 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1612 start_diff_command (_("delete note"));
1613 _diff_command->remove (n);
1616 trackview.editor().hide_verbose_canvas_cursor ();
1620 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1622 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1623 if ((*i)->selected() && (*i) != ev) {
1624 (*i)->selected(false);
1625 (*i)->hide_velocity();
1633 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1635 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1638 Selection::iterator tmp = i;
1641 (*i)->selected (false);
1642 _selection.erase (i);
1651 /* don't bother with removing this regionview from the editor selection,
1652 since we're about to add another note, and thus put/keep this
1653 regionview in the editor selection.
1656 if (!ev->selected()) {
1657 add_to_selection (ev);
1662 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1664 uint8_t low_note = 127;
1665 uint8_t high_note = 0;
1666 MidiModel::Notes& notes (_model->notes());
1667 _optimization_iterator = _events.begin();
1673 if (extend && _selection.empty()) {
1679 /* scan existing selection to get note range */
1681 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1682 if ((*i)->note()->note() < low_note) {
1683 low_note = (*i)->note()->note();
1685 if ((*i)->note()->note() > high_note) {
1686 high_note = (*i)->note()->note();
1690 low_note = min (low_note, notenum);
1691 high_note = max (high_note, notenum);
1694 no_sound_notes = true;
1696 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1698 boost::shared_ptr<NoteType> note (*n);
1699 CanvasNoteEvent* cne;
1700 bool select = false;
1702 if (((1 << note->channel()) & channel_mask) != 0) {
1704 if ((note->note() >= low_note && note->note() <= high_note)) {
1707 } else if (note->note() == notenum) {
1713 if ((cne = find_canvas_note (note)) != 0) {
1714 // extend is false because we've taken care of it,
1715 // since it extends by time range, not pitch.
1716 note_selected (cne, add, false);
1720 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1724 no_sound_notes = false;
1728 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1730 MidiModel::Notes& notes (_model->notes());
1731 _optimization_iterator = _events.begin();
1733 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1735 boost::shared_ptr<NoteType> note (*n);
1736 CanvasNoteEvent* cne;
1738 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1739 if ((cne = find_canvas_note (note)) != 0) {
1740 if (cne->selected()) {
1741 note_deselected (cne);
1743 note_selected (cne, true, false);
1751 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1754 clear_selection_except(ev);
1759 if (!ev->selected()) {
1760 add_to_selection (ev);
1764 /* find end of latest note selected, select all between that and the start of "ev" */
1766 Evoral::MusicalTime earliest = DBL_MAX;
1767 Evoral::MusicalTime latest = 0;
1769 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1770 if ((*i)->note()->end_time() > latest) {
1771 latest = (*i)->note()->end_time();
1773 if ((*i)->note()->time() < earliest) {
1774 earliest = (*i)->note()->time();
1778 if (ev->note()->end_time() > latest) {
1779 latest = ev->note()->end_time();
1782 if (ev->note()->time() < earliest) {
1783 earliest = ev->note()->time();
1786 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1788 /* find notes entirely within OR spanning the earliest..latest range */
1790 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1791 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1792 add_to_selection (*i);
1796 /* if events were guaranteed to be time sorted, we could do this.
1797 but as of sept 10th 2009, they no longer are.
1800 if ((*i)->note()->time() > latest) {
1809 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1811 remove_from_selection (ev);
1815 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1825 // TODO: Make this faster by storing the last updated selection rect, and only
1826 // adjusting things that are in the area that appears/disappeared.
1827 // We probably need a tree to be able to find events in O(log(n)) time.
1829 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1831 /* check if any corner of the note is inside the rect
1834 1) this is computing "touched by", not "contained by" the rect.
1835 2) this does not require that events be sorted in time.
1838 const double ix1 = (*i)->x1();
1839 const double ix2 = (*i)->x2();
1840 const double iy1 = (*i)->y1();
1841 const double iy2 = (*i)->y2();
1843 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1844 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1845 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1846 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1849 if (!(*i)->selected()) {
1850 add_to_selection (*i);
1852 } else if ((*i)->selected()) {
1853 // Not inside rectangle
1854 remove_from_selection (*i);
1860 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1862 Selection::iterator i = _selection.find (ev);
1864 if (i != _selection.end()) {
1865 _selection.erase (i);
1868 ev->selected (false);
1869 ev->hide_velocity ();
1871 if (_selection.empty()) {
1872 PublicEditor& editor (trackview.editor());
1873 editor.get_selection().remove (this);
1878 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1880 bool add_mrv_selection = false;
1882 if (_selection.empty()) {
1883 add_mrv_selection = true;
1886 if (_selection.insert (ev).second) {
1887 ev->selected (true);
1888 play_midi_note ((ev)->note());
1891 if (add_mrv_selection) {
1892 PublicEditor& editor (trackview.editor());
1893 editor.get_selection().add (this);
1898 MidiRegionView::move_selection(double dx, double dy)
1900 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1901 (*i)->move_event(dx, dy);
1906 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1908 assert (!_selection.empty());
1910 uint8_t lowest_note_in_selection = 127;
1911 uint8_t highest_note_in_selection = 0;
1912 uint8_t highest_note_difference = 0;
1914 // find highest and lowest notes first
1916 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1917 uint8_t pitch = (*i)->note()->note();
1918 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1919 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1923 cerr << "dnote: " << (int) dnote << endl;
1924 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1925 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1926 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1927 << int(highest_note_in_selection) << endl;
1928 cerr << "selection size: " << _selection.size() << endl;
1929 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1932 // Make sure the note pitch does not exceed the MIDI standard range
1933 if (highest_note_in_selection + dnote > 127) {
1934 highest_note_difference = highest_note_in_selection - 127;
1937 start_diff_command(_("move notes"));
1939 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1941 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1944 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1946 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1949 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1955 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1957 uint8_t original_pitch = (*i)->note()->note();
1958 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1960 // keep notes in standard midi range
1961 clamp_to_0_127(new_pitch);
1963 // keep original pitch if note is dragged outside valid midi range
1964 if ((original_pitch != 0 && new_pitch == 0)
1965 || (original_pitch != 127 && new_pitch == 127)) {
1966 new_pitch = original_pitch;
1969 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1970 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1972 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1977 // care about notes being moved beyond the upper/lower bounds on the canvas
1978 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1979 highest_note_in_selection > midi_stream_view()->highest_note()) {
1980 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1985 MidiRegionView::snap_pixel_to_frame(double x)
1987 PublicEditor& editor = trackview.editor();
1988 // x is region relative, convert it to global absolute frames
1989 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1990 editor.snap_to(frame);
1991 return frame - _region->position(); // convert back to region relative
1995 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1997 PublicEditor& editor = trackview.editor();
1998 // x is region relative, convert it to global absolute frames
1999 nframes64_t frame = x + _region->position();
2000 editor.snap_to(frame);
2001 return frame - _region->position(); // convert back to region relative
2005 MidiRegionView::snap_to_pixel(double x)
2007 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2011 MidiRegionView::get_position_pixels()
2013 nframes64_t region_frame = get_position();
2014 return trackview.editor().frame_to_pixel(region_frame);
2018 MidiRegionView::get_end_position_pixels()
2020 nframes64_t frame = get_position() + get_duration ();
2021 return trackview.editor().frame_to_pixel(frame);
2025 MidiRegionView::beats_to_frames(double beats) const
2027 return _time_converter.to(beats);
2031 MidiRegionView::frames_to_beats(nframes64_t frames) const
2033 return _time_converter.from(frames);
2037 MidiRegionView::begin_resizing (bool /*at_front*/)
2039 _resize_data.clear();
2041 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2042 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2044 // only insert CanvasNotes into the map
2046 NoteResizeData *resize_data = new NoteResizeData();
2047 resize_data->canvas_note = note;
2049 // create a new SimpleRect from the note which will be the resize preview
2050 SimpleRect *resize_rect = new SimpleRect(
2051 *group, note->x1(), note->y1(), note->x2(), note->y2());
2053 // calculate the colors: get the color settings
2054 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2055 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2058 // make the resize preview notes more transparent and bright
2059 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2061 // calculate color based on note velocity
2062 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2063 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
2067 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2068 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2070 resize_data->resize_rect = resize_rect;
2071 _resize_data.push_back(resize_data);
2076 /** Update resizing notes while user drags.
2077 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2078 * @param at_front which end of the note (true == note on, false == note off)
2079 * @param delta_x change in mouse position since the start of the drag
2080 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2081 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2082 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2083 * as the \a primary note.
2086 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2088 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2089 SimpleRect* resize_rect = (*i)->resize_rect;
2090 CanvasNote* canvas_note = (*i)->canvas_note;
2095 current_x = canvas_note->x1() + delta_x;
2097 current_x = primary->x1() + delta_x;
2101 current_x = canvas_note->x2() + delta_x;
2103 current_x = primary->x2() + delta_x;
2108 resize_rect->property_x1() = snap_to_pixel(current_x);
2109 resize_rect->property_x2() = canvas_note->x2();
2111 resize_rect->property_x2() = snap_to_pixel(current_x);
2112 resize_rect->property_x1() = canvas_note->x1();
2118 /** Finish resizing notes when the user releases the mouse button.
2119 * Parameters the same as for \a update_resizing().
2122 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2124 start_diff_command(_("resize notes"));
2126 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2127 CanvasNote* canvas_note = (*i)->canvas_note;
2128 SimpleRect* resize_rect = (*i)->resize_rect;
2133 current_x = canvas_note->x1() + delta_x;
2135 current_x = primary->x1() + delta_x;
2139 current_x = canvas_note->x2() + delta_x;
2141 current_x = primary->x2() + delta_x;
2145 current_x = snap_pixel_to_frame (current_x);
2146 current_x = frames_to_beats (current_x);
2148 if (at_front && current_x < canvas_note->note()->end_time()) {
2149 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2151 double len = canvas_note->note()->time() - current_x;
2152 len += canvas_note->note()->length();
2155 /* XXX convert to beats */
2156 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2161 double len = current_x - canvas_note->note()->time();
2164 /* XXX convert to beats */
2165 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2173 _resize_data.clear();
2178 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2180 uint8_t new_velocity;
2183 new_velocity = event->note()->velocity() + velocity;
2184 clamp_to_0_127(new_velocity);
2186 new_velocity = velocity;
2189 event->show_velocity ();
2191 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2195 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2200 new_note = event->note()->note() + note;
2205 clamp_to_0_127 (new_note);
2206 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2210 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2212 bool change_start = false;
2213 bool change_length = false;
2214 Evoral::MusicalTime new_start;
2215 Evoral::MusicalTime new_length;
2217 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2219 front_delta: if positive - move the start of the note later in time (shortening it)
2220 if negative - move the start of the note earlier in time (lengthening it)
2222 end_delta: if positive - move the end of the note later in time (lengthening it)
2223 if negative - move the end of the note earlier in time (shortening it)
2227 if (front_delta < 0) {
2229 if (event->note()->time() < -front_delta) {
2232 new_start = event->note()->time() + front_delta; // moves earlier
2235 /* start moved toward zero, so move the end point out to where it used to be.
2236 Note that front_delta is negative, so this increases the length.
2239 new_length = event->note()->length() - front_delta;
2240 change_start = true;
2241 change_length = true;
2245 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2247 if (new_pos < event->note()->end_time()) {
2248 new_start = event->note()->time() + front_delta;
2249 /* start moved toward the end, so move the end point back to where it used to be */
2250 new_length = event->note()->length() - front_delta;
2251 change_start = true;
2252 change_length = true;
2259 bool can_change = true;
2260 if (end_delta < 0) {
2261 if (event->note()->length() < -end_delta) {
2267 new_length = event->note()->length() + end_delta;
2268 change_length = true;
2273 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2276 if (change_length) {
2277 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2282 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2284 Evoral::MusicalTime new_time;
2288 if (event->note()->time() < -delta) {
2291 new_time = event->note()->time() + delta;
2294 new_time = event->note()->time() + delta;
2300 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2304 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2308 if (_selection.empty()) {
2323 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2324 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2330 start_diff_command(_("change velocities"));
2332 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2333 Selection::iterator next = i;
2335 change_note_velocity (*i, delta, true);
2344 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2346 if (_selection.empty()) {
2363 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2365 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2369 if ((int8_t) (*i)->note()->note() + delta > 127) {
2376 start_diff_command (_("transpose"));
2378 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2379 Selection::iterator next = i;
2381 change_note_note (*i, delta, true);
2389 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2391 Evoral::MusicalTime delta;
2396 /* grab the current grid distance */
2398 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2400 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2401 cerr << "Grid type not available as beats - TO BE FIXED\n";
2410 start_diff_command (_("change note lengths"));
2412 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2413 Selection::iterator next = i;
2416 /* note the negation of the delta for start */
2418 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2427 MidiRegionView::nudge_notes (bool forward)
2429 if (_selection.empty()) {
2433 /* pick a note as the point along the timeline to get the nudge distance.
2434 its not necessarily the earliest note, so we may want to pull the notes out
2435 into a vector and sort before using the first one.
2438 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2440 nframes64_t distance;
2442 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2444 /* grid is off - use nudge distance */
2446 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2452 nframes64_t next_pos = ref_point;
2455 /* XXX need check on max_frames, but that needs max_frames64 or something */
2458 if (next_pos == 0) {
2464 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2465 distance = ref_point - next_pos;
2468 if (distance == 0) {
2472 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2478 start_diff_command (_("nudge"));
2480 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2481 Selection::iterator next = i;
2483 change_note_time (*i, delta, true);
2491 MidiRegionView::change_channel(uint8_t channel)
2493 start_diff_command(_("change channel"));
2494 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2495 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2503 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2505 if (_mouse_state == SelectTouchDragging) {
2506 note_selected (ev, true);
2509 show_verbose_canvas_cursor (ev->note ());
2513 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2515 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2516 (*i)->hide_velocity ();
2519 trackview.editor().hide_verbose_canvas_cursor ();
2523 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2525 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2527 display_model(msrc->model());
2531 MidiRegionView::set_frame_color()
2534 if (_selected && should_show_selection) {
2535 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2537 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2543 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2547 case FilterChannels:
2548 _force_channel = -1;
2551 _force_channel = mask;
2552 mask = 0xFFFF; // Show all notes as active (below)
2555 // Update notes for selection
2556 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2557 (*i)->on_channel_selection_change(mask);
2560 _last_channel_selection = mask;
2564 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2566 _model_name = model;
2567 _custom_device_mode = custom_device_mode;
2572 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2574 if (_selection.empty()) {
2578 PublicEditor& editor (trackview.editor());
2583 editor.get_cut_buffer().add (selection_as_cut_buffer());
2591 start_diff_command();
2593 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2599 diff_remove_note (*i);
2609 MidiRegionView::selection_as_cut_buffer () const
2613 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2614 NoteType* n = (*i)->note().get();
2615 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2618 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2625 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2631 start_diff_command (_("paste"));
2633 Evoral::MusicalTime beat_delta;
2634 Evoral::MusicalTime paste_pos_beats;
2635 Evoral::MusicalTime duration;
2636 Evoral::MusicalTime end_point;
2638 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2639 paste_pos_beats = frames_to_beats (pos - _region->position());
2640 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2641 paste_pos_beats = 0;
2643 _selection.clear ();
2645 for (int n = 0; n < (int) times; ++n) {
2647 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2649 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2650 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2652 /* make all newly added notes selected */
2654 diff_add_note (copied_note, true);
2655 end_point = copied_note->end_time();
2658 paste_pos_beats += duration;
2661 /* if we pasted past the current end of the region, extend the region */
2663 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2664 nframes64_t region_end = _region->position() + _region->length() - 1;
2666 if (end_frame > region_end) {
2668 trackview.session()->begin_reversible_command (_("paste"));
2670 _region->clear_history ();
2671 _region->set_length (end_frame, this);
2672 trackview.session()->add_command (new StatefulDiffCommand (_region));
2678 struct EventNoteTimeEarlyFirstComparator {
2679 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2680 return a->note()->time() < b->note()->time();
2685 MidiRegionView::time_sort_events ()
2687 if (!_sort_needed) {
2691 EventNoteTimeEarlyFirstComparator cmp;
2694 _sort_needed = false;
2698 MidiRegionView::goto_next_note ()
2700 // nframes64_t pos = -1;
2701 bool use_next = false;
2703 if (_events.back()->selected()) {
2707 time_sort_events ();
2709 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2710 if ((*i)->selected()) {
2713 } else if (use_next) {
2715 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2720 /* use the first one */
2722 unique_select (_events.front());
2727 MidiRegionView::goto_previous_note ()
2729 // nframes64_t pos = -1;
2730 bool use_next = false;
2732 if (_events.front()->selected()) {
2736 time_sort_events ();
2738 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2739 if ((*i)->selected()) {
2742 } else if (use_next) {
2744 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2749 /* use the last one */
2751 unique_select (*(_events.rbegin()));
2755 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2757 bool had_selected = false;
2759 time_sort_events ();
2761 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2762 if ((*i)->selected()) {
2763 selected.insert ((*i)->note());
2764 had_selected = true;
2768 if (allow_all_if_none_selected && !had_selected) {
2769 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2770 selected.insert ((*i)->note());
2776 MidiRegionView::update_ghost_note (double x, double y)
2782 nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2783 trackview.editor().snap_to (f);
2784 f -= _region->position ();
2787 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2792 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2794 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2795 _ghost_note->note()->set_length (length);
2796 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2798 update_note (_ghost_note);
2800 show_verbose_canvas_cursor (_ghost_note->note ());
2804 MidiRegionView::create_ghost_note (double x, double y)
2809 boost::shared_ptr<NoteType> g (new NoteType);
2810 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2811 update_ghost_note (x, y);
2812 _ghost_note->show ();
2817 show_verbose_canvas_cursor (_ghost_note->note ());
2821 MidiRegionView::snap_changed ()
2827 create_ghost_note (_last_ghost_x, _last_ghost_y);
2831 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2834 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (n->note()).c_str(), (int) n->note ());
2835 trackview.editor().show_verbose_canvas_cursor_with (buf);
2839 MidiRegionView::drop_down_keys ()
2841 _mouse_state = None;
2845 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
2847 double note = midi_stream_view()->y_to_note(y);
2849 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2851 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
2853 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
2854 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
2855 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
2856 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
2861 bool add_mrv_selection = false;
2863 if (_selection.empty()) {
2864 add_mrv_selection = true;
2867 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
2868 if (_selection.insert (*i).second) {
2869 (*i)->selected (true);
2873 if (add_mrv_selection) {
2874 PublicEditor& editor (trackview.editor());
2875 editor.get_selection().add (this);