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;
312 _pressed_button = ev->button;
318 MidiRegionView::button_release (GdkEventButton* ev)
320 double event_x, event_y;
321 nframes64_t event_frame = 0;
325 group->w2i(event_x, event_y);
326 group->ungrab(ev->time);
327 event_frame = trackview.editor().pixel_to_frame(event_x);
329 if (ev->button == 3) {
331 } else if (_pressed_button != 1) {
335 switch (_mouse_state) {
336 case Pressed: // Clicked
337 switch (trackview.editor().current_mouse_mode()) {
341 maybe_select_by_position (ev, event_x, event_y);
347 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 cerr << "MRV key press\n";
543 /* since GTK bindings are generally activated on press, and since
544 detectable auto-repeat is the name of the game and only sends
545 repeated presses, carry out key actions at key press, not release.
548 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
549 _mouse_state = SelectTouchDragging;
552 } else if (ev->keyval == GDK_Escape) {
556 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
558 bool start = (ev->keyval == GDK_comma);
559 bool end = (ev->keyval == GDK_period);
560 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
561 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
563 change_note_lengths (fine, shorter, start, end);
567 } else if (ev->keyval == GDK_Delete) {
572 } else if (ev->keyval == GDK_Tab) {
574 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
575 goto_previous_note ();
581 } else if (ev->keyval == GDK_Up) {
583 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
584 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
586 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
587 change_velocities (true, fine, allow_smush);
589 transpose (true, fine, allow_smush);
593 } else if (ev->keyval == GDK_Down) {
595 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
596 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
598 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
599 change_velocities (false, fine, allow_smush);
601 transpose (false, fine, allow_smush);
605 } else if (ev->keyval == GDK_Left) {
610 } else if (ev->keyval == GDK_Right) {
615 } else if (ev->keyval == GDK_Control_L) {
618 } else if (ev->keyval == GDK_r) {
619 /* yes, this steals r */
620 if (midi_view()->midi_track()->step_editing()) {
621 midi_view()->step_edit_rest ();
622 cerr << "Stole that r because " << midi_view()->midi_track()->name()
623 << " is step editing!\n";
632 MidiRegionView::key_release (GdkEventKey* ev)
634 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
642 MidiRegionView::show_list_editor ()
645 _list_editor = new MidiListEditor (trackview.session(), midi_region());
647 _list_editor->present ();
650 /** Add a note to the model, and the view, at a canvas (click) coordinate.
651 * \param x horizontal position in pixels
652 * \param y vertical position in pixels
653 * \param length duration of the note in beats */
655 MidiRegionView::create_note_at(double x, double y, double length)
657 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
658 MidiStreamView* const view = mtv->midi_view();
660 double note = midi_stream_view()->y_to_note(y);
663 assert(note <= 127.0);
665 // Start of note in frames relative to region start
666 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
667 assert(start_frames >= 0);
670 length = frames_to_beats(
671 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
673 assert (length != 0);
675 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
676 frames_to_beats(start_frames + _region->start()), length,
677 (uint8_t)note, 0x40));
679 if (_model->contains (new_note)) {
683 view->update_note_range(new_note->note());
685 MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
687 _model->apply_command(*trackview.session(), cmd);
689 play_midi_note (new_note);
693 MidiRegionView::clear_events()
698 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
699 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
704 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
709 _pgm_changes.clear();
711 _optimization_iterator = _events.end();
716 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
719 content_connection.disconnect ();
720 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
724 if (_enable_display) {
730 MidiRegionView::start_diff_command(string name)
732 if (!_diff_command) {
733 _diff_command = _model->new_diff_command(name);
738 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
741 _diff_command->add(note);
744 _marked_for_selection.insert(note);
747 _marked_for_velocity.insert(note);
752 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
754 if (_diff_command && ev->note()) {
755 _diff_command->remove(ev->note());
760 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
761 MidiModel::DiffCommand::Property property,
765 _diff_command->change (ev->note(), property, val);
770 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
771 MidiModel::DiffCommand::Property property,
772 Evoral::MusicalTime val)
775 _diff_command->change (ev->note(), property, val);
780 MidiRegionView::apply_diff ()
784 if (!_diff_command) {
788 if ((add_or_remove = _diff_command->adds_or_removes())) {
789 // Mark all selected notes for selection when model reloads
790 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
791 _marked_for_selection.insert((*i)->note());
795 _model->apply_command(*trackview.session(), _diff_command);
797 midi_view()->midi_track()->playlist_modified();
801 _marked_for_selection.clear();
804 _marked_for_velocity.clear();
808 MidiRegionView::apply_diff_as_subcommand()
812 if (!_diff_command) {
816 if ((add_or_remove = _diff_command->adds_or_removes())) {
817 // Mark all selected notes for selection when model reloads
818 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
819 _marked_for_selection.insert((*i)->note());
823 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
825 midi_view()->midi_track()->playlist_modified();
828 _marked_for_selection.clear();
830 _marked_for_velocity.clear();
835 MidiRegionView::abort_command()
837 delete _diff_command;
843 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
845 if (_optimization_iterator != _events.end()) {
846 ++_optimization_iterator;
849 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
850 return *_optimization_iterator;
853 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
854 if ((*_optimization_iterator)->note() == note) {
855 return *_optimization_iterator;
863 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
865 MidiModel::Notes notes;
866 _model->get_notes (notes, op, val, chan_mask);
868 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
869 CanvasNoteEvent* cne = find_canvas_note (*n);
877 MidiRegionView::redisplay_model()
879 // Don't redisplay the model if we're currently recording and displaying that
885 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
889 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
893 MidiModel::ReadLock lock(_model->read_lock());
895 MidiModel::Notes& notes (_model->notes());
896 _optimization_iterator = _events.begin();
898 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
900 boost::shared_ptr<NoteType> note (*n);
901 CanvasNoteEvent* cne;
904 if (note_in_region_range (note, visible)) {
906 if ((cne = find_canvas_note (note)) != 0) {
913 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
915 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
927 add_note (note, visible);
932 if ((cne = find_canvas_note (note)) != 0) {
940 /* remove note items that are no longer valid */
942 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
943 if (!(*i)->valid ()) {
945 i = _events.erase (i);
952 display_program_changes();
954 _marked_for_selection.clear ();
955 _marked_for_velocity.clear ();
957 /* we may have caused _events to contain things out of order (e.g. if a note
958 moved earlier or later). we don't generally need them in time order, but
959 make a note that a sort is required for those cases that require it.
966 MidiRegionView::display_program_changes()
968 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
973 Glib::Mutex::Lock lock (control->list()->lock());
975 uint8_t channel = control->parameter().channel();
977 for (AutomationList::const_iterator event = control->list()->begin();
978 event != control->list()->end(); ++event) {
979 double event_time = (*event)->when;
980 double program_number = floor((*event)->value + 0.5);
982 // Get current value of bank select MSB at time of the program change
983 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
984 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
986 if (msb_control != 0) {
987 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
990 // Get current value of bank select LSB at time of the program change
991 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
992 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
994 if (lsb_control != 0) {
995 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
998 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1000 boost::shared_ptr<MIDI::Name::Patch> patch =
1001 MIDI::Name::MidiPatchManager::instance().find_patch(
1002 _model_name, _custom_device_mode, channel, patch_key);
1004 PCEvent program_change(event_time, uint8_t(program_number), channel);
1007 add_pgm_change(program_change, patch->name());
1010 snprintf(buf, 4, "%d", int(program_number));
1011 add_pgm_change(program_change, buf);
1017 MidiRegionView::display_sysexes()
1019 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1020 Evoral::MusicalTime time = (*i)->time();
1025 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1026 str << int((*i)->buffer()[b]);
1027 if (b != (*i)->size() -1) {
1031 string text = str.str();
1033 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1035 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1037 double height = midi_stream_view()->contents_height();
1039 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1040 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1042 // Show unless program change is beyond the region bounds
1043 if (time - _region->start() >= _region->length() || time < _region->start()) {
1049 _sys_exes.push_back(sysex);
1054 MidiRegionView::~MidiRegionView ()
1056 in_destructor = true;
1058 note_delete_connection.disconnect ();
1060 delete _list_editor;
1062 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1064 if (_active_notes) {
1071 delete _diff_command;
1075 MidiRegionView::region_resized (const PropertyChange& what_changed)
1077 RegionView::region_resized(what_changed);
1079 if (what_changed.contains (ARDOUR::Properties::position)) {
1080 set_duration(_region->length(), 0);
1081 if (_enable_display) {
1088 MidiRegionView::reset_width_dependent_items (double pixel_width)
1090 RegionView::reset_width_dependent_items(pixel_width);
1091 assert(_pixel_width == pixel_width);
1093 if (_enable_display) {
1099 MidiRegionView::set_height (double height)
1101 static const double FUDGE = 2.0;
1102 const double old_height = _height;
1103 RegionView::set_height(height);
1104 _height = height - FUDGE;
1106 apply_note_range(midi_stream_view()->lowest_note(),
1107 midi_stream_view()->highest_note(),
1108 height != old_height + FUDGE);
1111 name_pixbuf->raise_to_top();
1116 /** Apply the current note range from the stream view
1117 * by repositioning/hiding notes as necessary
1120 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1122 if (!_enable_display) {
1126 if (!force && _current_range_min == min && _current_range_max == max) {
1130 _current_range_min = min;
1131 _current_range_max = max;
1133 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1134 CanvasNoteEvent* event = *i;
1135 boost::shared_ptr<NoteType> note (event->note());
1137 if (note->note() < _current_range_min ||
1138 note->note() > _current_range_max) {
1144 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1146 const double y1 = midi_stream_view()->note_to_y(note->note());
1147 const double y2 = y1 + floor(midi_stream_view()->note_height());
1149 cnote->property_y1() = y1;
1150 cnote->property_y2() = y2;
1152 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1154 double x = trackview.editor().frame_to_pixel(
1155 beats_to_frames(note->time()) - _region->start());
1156 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1157 double y = midi_stream_view()->note_to_y(event->note()->note())
1158 + ((diamond_size-2.0) / 4.0);
1160 chit->set_height (diamond_size);
1161 chit->move (x - chit->x1(), y - chit->y1());
1168 MidiRegionView::add_ghost (TimeAxisView& tv)
1172 double unit_position = _region->position () / samples_per_unit;
1173 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1174 MidiGhostRegion* ghost;
1176 if (mtv && mtv->midi_view()) {
1177 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1178 to allow having midi notes on top of note lines and waveforms.
1180 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1182 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1185 ghost->set_height ();
1186 ghost->set_duration (_region->length() / samples_per_unit);
1187 ghosts.push_back (ghost);
1189 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1190 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1191 ghost->add_note(note);
1195 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1201 /** Begin tracking note state for successive calls to add_event
1204 MidiRegionView::begin_write()
1206 assert(!_active_notes);
1207 _active_notes = new CanvasNote*[128];
1208 for (unsigned i=0; i < 128; ++i) {
1209 _active_notes[i] = 0;
1214 /** Destroy note state for add_event
1217 MidiRegionView::end_write()
1219 delete[] _active_notes;
1221 _marked_for_selection.clear();
1222 _marked_for_velocity.clear();
1226 /** Resolve an active MIDI note (while recording).
1229 MidiRegionView::resolve_note(uint8_t note, double end_time)
1231 if (midi_view()->note_mode() != Sustained) {
1235 if (_active_notes && _active_notes[note]) {
1236 const nframes64_t end_time_frames = beats_to_frames(end_time);
1237 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1238 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1239 _active_notes[note] = 0;
1244 /** Extend active notes to rightmost edge of region (if length is changed)
1247 MidiRegionView::extend_active_notes()
1249 if (!_active_notes) {
1253 for (unsigned i=0; i < 128; ++i) {
1254 if (_active_notes[i]) {
1255 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1261 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1263 if (no_sound_notes || !trackview.editor().sound_notes()) {
1267 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1270 route_ui->midi_track()->write_immediate_event(
1271 note->on_event().size(), note->on_event().buffer());
1273 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1274 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1275 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1276 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1277 note_length_ms, G_PRIORITY_DEFAULT);
1281 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1283 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1286 route_ui->midi_track()->write_immediate_event(
1287 note->off_event().size(), note->off_event().buffer());
1293 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1295 const nframes64_t note_start_frames = beats_to_frames(note->time());
1297 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1298 (note_start_frames < _region->start());
1300 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1301 (note->note() <= midi_stream_view()->highest_note());
1307 MidiRegionView::update_note (CanvasNote* ev)
1309 boost::shared_ptr<NoteType> note = ev->note();
1311 const nframes64_t note_start_frames = beats_to_frames(note->time());
1312 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1314 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1315 const double y1 = midi_stream_view()->note_to_y(note->note());
1316 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1318 ev->property_x1() = x;
1319 ev->property_y1() = y1;
1320 if (note->length() > 0) {
1321 ev->property_x2() = note_endpixel;
1323 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1325 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1327 if (note->length() == 0) {
1328 if (_active_notes) {
1329 assert(note->note() < 128);
1330 // If this note is already active there's a stuck note,
1331 // finish the old note rectangle
1332 if (_active_notes[note->note()]) {
1333 CanvasNote* const old_rect = _active_notes[note->note()];
1334 boost::shared_ptr<NoteType> old_note = old_rect->note();
1335 old_rect->property_x2() = x;
1336 old_rect->property_outline_what() = (guint32) 0xF;
1338 _active_notes[note->note()] = ev;
1340 /* outline all but right edge */
1341 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1343 /* outline all edges */
1344 ev->property_outline_what() = (guint32) 0xF;
1349 MidiRegionView::update_hit (CanvasHit* ev)
1351 boost::shared_ptr<NoteType> note = ev->note();
1353 const nframes64_t note_start_frames = beats_to_frames(note->time());
1354 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1355 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1356 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1361 /** Add a MIDI note to the view (with length).
1363 * If in sustained mode, notes with length 0 will be considered active
1364 * notes, and resolve_note should be called when the corresponding note off
1365 * event arrives, to properly display the note.
1368 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1370 CanvasNoteEvent* event = 0;
1372 assert(note->time() >= 0);
1373 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1375 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1377 if (midi_view()->note_mode() == Sustained) {
1379 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1381 update_note (ev_rect);
1385 MidiGhostRegion* gr;
1387 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1388 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1389 gr->add_note(ev_rect);
1393 } else if (midi_view()->note_mode() == Percussive) {
1395 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1397 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1399 update_hit (ev_diamond);
1408 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1409 note_selected(event, true);
1412 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1413 event->show_velocity();
1415 event->on_channel_selection_change(_last_channel_selection);
1416 _events.push_back(event);
1427 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1428 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1430 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1432 start_diff_command (_("step add"));
1433 diff_add_note (new_note, true, false);
1436 /* potentially extend region to hold new note */
1438 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1439 nframes64_t region_end = _region->position() + _region->length() - 1;
1441 if (end_frame > region_end) {
1442 _region->set_length (end_frame, this);
1449 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1451 assert(program.time >= 0);
1453 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1454 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1456 double height = midi_stream_view()->contents_height();
1458 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1459 new CanvasProgramChange(*this, *group,
1464 _custom_device_mode,
1465 program.time, program.channel, program.value));
1467 // Show unless program change is beyond the region bounds
1468 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1474 _pgm_changes.push_back(pgm_change);
1478 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1480 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1481 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1482 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1484 if (msb_control != 0) {
1485 msb = int(msb_control->get_float(true, time));
1486 cerr << "got msb " << msb;
1489 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1490 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1492 if (lsb_control != 0) {
1493 lsb = lsb_control->get_float(true, time);
1494 cerr << " got lsb " << lsb;
1497 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1498 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1499 float program_number = -1.0;
1500 if (program_control != 0) {
1501 program_number = program_control->get_float(true, time);
1502 cerr << " got program " << program_number << endl;
1505 key.msb = (int) floor(msb + 0.5);
1506 key.lsb = (int) floor(lsb + 0.5);
1507 key.program_number = (int) floor(program_number + 0.5);
1508 assert(key.is_sane());
1513 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1515 // TODO: Get the real event here and alter them at the original times
1516 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1517 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1518 if (msb_control != 0) {
1519 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1522 // TODO: Get the real event here and alter them at the original times
1523 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1524 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1525 if (lsb_control != 0) {
1526 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1529 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1530 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1532 assert(program_control != 0);
1533 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1539 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1541 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1542 alter_program_change(program_change_event, new_patch);
1546 MidiRegionView::previous_program(CanvasProgramChange& program)
1548 MIDI::Name::PatchPrimaryKey key;
1549 get_patch_key_at(program.event_time(), program.channel(), key);
1551 boost::shared_ptr<MIDI::Name::Patch> patch =
1552 MIDI::Name::MidiPatchManager::instance().previous_patch(
1554 _custom_device_mode,
1558 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1560 alter_program_change(program_change_event, patch->patch_primary_key());
1565 MidiRegionView::next_program(CanvasProgramChange& program)
1567 MIDI::Name::PatchPrimaryKey key;
1568 get_patch_key_at(program.event_time(), program.channel(), key);
1570 boost::shared_ptr<MIDI::Name::Patch> patch =
1571 MIDI::Name::MidiPatchManager::instance().next_patch(
1573 _custom_device_mode,
1577 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1579 alter_program_change(program_change_event, patch->patch_primary_key());
1584 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1586 if (_selection.empty()) {
1590 if (_selection.erase (cne) > 0) {
1591 cerr << "Erased a CNE from selection\n";
1596 MidiRegionView::delete_selection()
1598 if (_selection.empty()) {
1602 start_diff_command (_("delete selection"));
1604 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1605 if ((*i)->selected()) {
1606 _diff_command->remove((*i)->note());
1616 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1618 start_diff_command (_("delete note"));
1619 _diff_command->remove (n);
1622 trackview.editor().hide_verbose_canvas_cursor ();
1626 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1628 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1629 if ((*i)->selected() && (*i) != ev) {
1630 (*i)->selected(false);
1631 (*i)->hide_velocity();
1639 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1641 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1644 Selection::iterator tmp = i;
1647 (*i)->selected (false);
1648 _selection.erase (i);
1657 /* don't bother with removing this regionview from the editor selection,
1658 since we're about to add another note, and thus put/keep this
1659 regionview in the editor selection.
1662 if (!ev->selected()) {
1663 add_to_selection (ev);
1668 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1670 uint8_t low_note = 127;
1671 uint8_t high_note = 0;
1672 MidiModel::Notes& notes (_model->notes());
1673 _optimization_iterator = _events.begin();
1679 if (extend && _selection.empty()) {
1685 /* scan existing selection to get note range */
1687 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1688 if ((*i)->note()->note() < low_note) {
1689 low_note = (*i)->note()->note();
1691 if ((*i)->note()->note() > high_note) {
1692 high_note = (*i)->note()->note();
1696 low_note = min (low_note, notenum);
1697 high_note = max (high_note, notenum);
1700 no_sound_notes = true;
1702 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1704 boost::shared_ptr<NoteType> note (*n);
1705 CanvasNoteEvent* cne;
1706 bool select = false;
1708 if (((1 << note->channel()) & channel_mask) != 0) {
1710 if ((note->note() >= low_note && note->note() <= high_note)) {
1713 } else if (note->note() == notenum) {
1719 if ((cne = find_canvas_note (note)) != 0) {
1720 // extend is false because we've taken care of it,
1721 // since it extends by time range, not pitch.
1722 note_selected (cne, add, false);
1726 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1730 no_sound_notes = false;
1734 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1736 MidiModel::Notes& notes (_model->notes());
1737 _optimization_iterator = _events.begin();
1739 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1741 boost::shared_ptr<NoteType> note (*n);
1742 CanvasNoteEvent* cne;
1744 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1745 if ((cne = find_canvas_note (note)) != 0) {
1746 if (cne->selected()) {
1747 note_deselected (cne);
1749 note_selected (cne, true, false);
1757 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1760 clear_selection_except(ev);
1765 if (!ev->selected()) {
1766 add_to_selection (ev);
1770 /* find end of latest note selected, select all between that and the start of "ev" */
1772 Evoral::MusicalTime earliest = DBL_MAX;
1773 Evoral::MusicalTime latest = 0;
1775 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1776 if ((*i)->note()->end_time() > latest) {
1777 latest = (*i)->note()->end_time();
1779 if ((*i)->note()->time() < earliest) {
1780 earliest = (*i)->note()->time();
1784 if (ev->note()->end_time() > latest) {
1785 latest = ev->note()->end_time();
1788 if (ev->note()->time() < earliest) {
1789 earliest = ev->note()->time();
1792 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1794 /* find notes entirely within OR spanning the earliest..latest range */
1796 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1797 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1798 add_to_selection (*i);
1802 /* if events were guaranteed to be time sorted, we could do this.
1803 but as of sept 10th 2009, they no longer are.
1806 if ((*i)->note()->time() > latest) {
1815 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1817 remove_from_selection (ev);
1821 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1831 // TODO: Make this faster by storing the last updated selection rect, and only
1832 // adjusting things that are in the area that appears/disappeared.
1833 // We probably need a tree to be able to find events in O(log(n)) time.
1835 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1837 /* check if any corner of the note is inside the rect
1840 1) this is computing "touched by", not "contained by" the rect.
1841 2) this does not require that events be sorted in time.
1844 const double ix1 = (*i)->x1();
1845 const double ix2 = (*i)->x2();
1846 const double iy1 = (*i)->y1();
1847 const double iy2 = (*i)->y2();
1849 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1850 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1851 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1852 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1855 if (!(*i)->selected()) {
1856 add_to_selection (*i);
1858 } else if ((*i)->selected()) {
1859 // Not inside rectangle
1860 remove_from_selection (*i);
1866 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1868 Selection::iterator i = _selection.find (ev);
1870 if (i != _selection.end()) {
1871 _selection.erase (i);
1874 ev->selected (false);
1875 ev->hide_velocity ();
1877 if (_selection.empty()) {
1878 PublicEditor& editor (trackview.editor());
1879 editor.get_selection().remove (this);
1884 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1886 bool add_mrv_selection = false;
1888 if (_selection.empty()) {
1889 add_mrv_selection = true;
1892 if (_selection.insert (ev).second) {
1893 ev->selected (true);
1894 play_midi_note ((ev)->note());
1897 if (add_mrv_selection) {
1898 PublicEditor& editor (trackview.editor());
1899 editor.get_selection().add (this);
1904 MidiRegionView::move_selection(double dx, double dy)
1906 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1907 (*i)->move_event(dx, dy);
1912 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1914 assert (!_selection.empty());
1916 uint8_t lowest_note_in_selection = 127;
1917 uint8_t highest_note_in_selection = 0;
1918 uint8_t highest_note_difference = 0;
1920 // find highest and lowest notes first
1922 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1923 uint8_t pitch = (*i)->note()->note();
1924 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1925 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1929 cerr << "dnote: " << (int) dnote << endl;
1930 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1931 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1932 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1933 << int(highest_note_in_selection) << endl;
1934 cerr << "selection size: " << _selection.size() << endl;
1935 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1938 // Make sure the note pitch does not exceed the MIDI standard range
1939 if (highest_note_in_selection + dnote > 127) {
1940 highest_note_difference = highest_note_in_selection - 127;
1943 start_diff_command(_("move notes"));
1945 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1947 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1950 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1952 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1955 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1961 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1963 uint8_t original_pitch = (*i)->note()->note();
1964 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1966 // keep notes in standard midi range
1967 clamp_to_0_127(new_pitch);
1969 // keep original pitch if note is dragged outside valid midi range
1970 if ((original_pitch != 0 && new_pitch == 0)
1971 || (original_pitch != 127 && new_pitch == 127)) {
1972 new_pitch = original_pitch;
1975 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1976 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1978 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1983 // care about notes being moved beyond the upper/lower bounds on the canvas
1984 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1985 highest_note_in_selection > midi_stream_view()->highest_note()) {
1986 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1991 MidiRegionView::snap_pixel_to_frame(double x)
1993 PublicEditor& editor = trackview.editor();
1994 // x is region relative, convert it to global absolute frames
1995 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1996 editor.snap_to(frame);
1997 return frame - _region->position(); // convert back to region relative
2001 MidiRegionView::snap_frame_to_frame(nframes64_t x)
2003 PublicEditor& editor = trackview.editor();
2004 // x is region relative, convert it to global absolute frames
2005 nframes64_t frame = x + _region->position();
2006 editor.snap_to(frame);
2007 return frame - _region->position(); // convert back to region relative
2011 MidiRegionView::snap_to_pixel(double x)
2013 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2017 MidiRegionView::get_position_pixels()
2019 nframes64_t region_frame = get_position();
2020 return trackview.editor().frame_to_pixel(region_frame);
2024 MidiRegionView::get_end_position_pixels()
2026 nframes64_t frame = get_position() + get_duration ();
2027 return trackview.editor().frame_to_pixel(frame);
2031 MidiRegionView::beats_to_frames(double beats) const
2033 return _time_converter.to(beats);
2037 MidiRegionView::frames_to_beats(nframes64_t frames) const
2039 return _time_converter.from(frames);
2043 MidiRegionView::begin_resizing (bool /*at_front*/)
2045 _resize_data.clear();
2047 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2048 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2050 // only insert CanvasNotes into the map
2052 NoteResizeData *resize_data = new NoteResizeData();
2053 resize_data->canvas_note = note;
2055 // create a new SimpleRect from the note which will be the resize preview
2056 SimpleRect *resize_rect = new SimpleRect(
2057 *group, note->x1(), note->y1(), note->x2(), note->y2());
2059 // calculate the colors: get the color settings
2060 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2061 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2064 // make the resize preview notes more transparent and bright
2065 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2067 // calculate color based on note velocity
2068 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2069 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
2073 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2074 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2076 resize_data->resize_rect = resize_rect;
2077 _resize_data.push_back(resize_data);
2082 /** Update resizing notes while user drags.
2083 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2084 * @param at_front which end of the note (true == note on, false == note off)
2085 * @param delta_x change in mouse position since the start of the drag
2086 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2087 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2088 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2089 * as the \a primary note.
2092 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2094 bool cursor_set = false;
2096 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2097 SimpleRect* resize_rect = (*i)->resize_rect;
2098 CanvasNote* canvas_note = (*i)->canvas_note;
2103 current_x = canvas_note->x1() + delta_x;
2105 current_x = primary->x1() + delta_x;
2109 current_x = canvas_note->x2() + delta_x;
2111 current_x = primary->x2() + delta_x;
2116 resize_rect->property_x1() = snap_to_pixel(current_x);
2117 resize_rect->property_x2() = canvas_note->x2();
2119 resize_rect->property_x2() = snap_to_pixel(current_x);
2120 resize_rect->property_x1() = canvas_note->x1();
2126 beats = snap_pixel_to_frame (current_x);
2127 beats = frames_to_beats (beats);
2132 if (beats < canvas_note->note()->end_time()) {
2133 len = canvas_note->note()->time() - beats;
2134 len += canvas_note->note()->length();
2139 if (beats >= canvas_note->note()->end_time()) {
2140 len = beats - canvas_note->note()->time();
2147 snprintf (buf, sizeof (buf), "%.3g beats", len);
2148 trackview.editor().show_verbose_canvas_cursor_with (buf);
2157 /** Finish resizing notes when the user releases the mouse button.
2158 * Parameters the same as for \a update_resizing().
2161 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2163 start_diff_command(_("resize notes"));
2165 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2166 CanvasNote* canvas_note = (*i)->canvas_note;
2167 SimpleRect* resize_rect = (*i)->resize_rect;
2172 current_x = canvas_note->x1() + delta_x;
2174 current_x = primary->x1() + delta_x;
2178 current_x = canvas_note->x2() + delta_x;
2180 current_x = primary->x2() + delta_x;
2184 current_x = snap_pixel_to_frame (current_x);
2185 current_x = frames_to_beats (current_x);
2187 if (at_front && current_x < canvas_note->note()->end_time()) {
2188 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2190 double len = canvas_note->note()->time() - current_x;
2191 len += canvas_note->note()->length();
2194 /* XXX convert to beats */
2195 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2200 double len = current_x - canvas_note->note()->time();
2203 /* XXX convert to beats */
2204 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2212 _resize_data.clear();
2217 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2219 uint8_t new_velocity;
2222 new_velocity = event->note()->velocity() + velocity;
2223 clamp_to_0_127(new_velocity);
2225 new_velocity = velocity;
2228 // event->show_velocity ();
2230 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2234 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2239 new_note = event->note()->note() + note;
2244 clamp_to_0_127 (new_note);
2245 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2249 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2251 bool change_start = false;
2252 bool change_length = false;
2253 Evoral::MusicalTime new_start;
2254 Evoral::MusicalTime new_length;
2256 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2258 front_delta: if positive - move the start of the note later in time (shortening it)
2259 if negative - move the start of the note earlier in time (lengthening it)
2261 end_delta: if positive - move the end of the note later in time (lengthening it)
2262 if negative - move the end of the note earlier in time (shortening it)
2266 if (front_delta < 0) {
2268 if (event->note()->time() < -front_delta) {
2271 new_start = event->note()->time() + front_delta; // moves earlier
2274 /* start moved toward zero, so move the end point out to where it used to be.
2275 Note that front_delta is negative, so this increases the length.
2278 new_length = event->note()->length() - front_delta;
2279 change_start = true;
2280 change_length = true;
2284 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2286 if (new_pos < event->note()->end_time()) {
2287 new_start = event->note()->time() + front_delta;
2288 /* start moved toward the end, so move the end point back to where it used to be */
2289 new_length = event->note()->length() - front_delta;
2290 change_start = true;
2291 change_length = true;
2298 bool can_change = true;
2299 if (end_delta < 0) {
2300 if (event->note()->length() < -end_delta) {
2306 new_length = event->note()->length() + end_delta;
2307 change_length = true;
2312 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2315 if (change_length) {
2316 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2321 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2323 Evoral::MusicalTime new_time;
2327 if (event->note()->time() < -delta) {
2330 new_time = event->note()->time() + delta;
2333 new_time = event->note()->time() + delta;
2339 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2343 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2347 if (_selection.empty()) {
2362 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2363 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2369 start_diff_command(_("change velocities"));
2371 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2372 Selection::iterator next = i;
2374 change_note_velocity (*i, delta, true);
2378 if (!_selection.empty()) {
2380 snprintf (buf, sizeof (buf), "Vel %d",
2381 (int) (*_selection.begin())->note()->velocity());
2382 trackview.editor().show_verbose_canvas_cursor_with (buf);
2390 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2392 if (_selection.empty()) {
2409 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2411 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2415 if ((int8_t) (*i)->note()->note() + delta > 127) {
2422 start_diff_command (_("transpose"));
2424 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2425 Selection::iterator next = i;
2427 change_note_note (*i, delta, true);
2435 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2437 Evoral::MusicalTime delta;
2442 /* grab the current grid distance */
2444 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2446 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2447 cerr << "Grid type not available as beats - TO BE FIXED\n";
2456 start_diff_command (_("change note lengths"));
2458 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2459 Selection::iterator next = i;
2462 /* note the negation of the delta for start */
2464 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2473 MidiRegionView::nudge_notes (bool forward)
2475 if (_selection.empty()) {
2479 /* pick a note as the point along the timeline to get the nudge distance.
2480 its not necessarily the earliest note, so we may want to pull the notes out
2481 into a vector and sort before using the first one.
2484 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2486 nframes64_t distance;
2488 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2490 /* grid is off - use nudge distance */
2492 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2498 nframes64_t next_pos = ref_point;
2501 /* XXX need check on max_frames, but that needs max_frames64 or something */
2504 if (next_pos == 0) {
2510 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2511 distance = ref_point - next_pos;
2514 if (distance == 0) {
2518 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2524 start_diff_command (_("nudge"));
2526 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2527 Selection::iterator next = i;
2529 change_note_time (*i, delta, true);
2537 MidiRegionView::change_channel(uint8_t channel)
2539 start_diff_command(_("change channel"));
2540 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2541 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2549 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2551 if (_mouse_state == SelectTouchDragging) {
2552 note_selected (ev, true);
2555 show_verbose_canvas_cursor (ev->note ());
2559 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2561 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2562 (*i)->hide_velocity ();
2565 trackview.editor().hide_verbose_canvas_cursor ();
2569 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2571 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2573 display_model(msrc->model());
2577 MidiRegionView::set_frame_color()
2580 if (_selected && should_show_selection) {
2581 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2583 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2589 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2593 case FilterChannels:
2594 _force_channel = -1;
2597 _force_channel = mask;
2598 mask = 0xFFFF; // Show all notes as active (below)
2601 // Update notes for selection
2602 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2603 (*i)->on_channel_selection_change(mask);
2606 _last_channel_selection = mask;
2610 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2612 _model_name = model;
2613 _custom_device_mode = custom_device_mode;
2618 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2620 if (_selection.empty()) {
2624 PublicEditor& editor (trackview.editor());
2629 editor.get_cut_buffer().add (selection_as_cut_buffer());
2637 start_diff_command();
2639 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2645 diff_remove_note (*i);
2655 MidiRegionView::selection_as_cut_buffer () const
2659 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2660 NoteType* n = (*i)->note().get();
2661 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2664 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2671 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2677 start_diff_command (_("paste"));
2679 Evoral::MusicalTime beat_delta;
2680 Evoral::MusicalTime paste_pos_beats;
2681 Evoral::MusicalTime duration;
2682 Evoral::MusicalTime end_point;
2684 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2685 paste_pos_beats = frames_to_beats (pos - _region->position());
2686 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2687 paste_pos_beats = 0;
2689 _selection.clear ();
2691 for (int n = 0; n < (int) times; ++n) {
2693 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2695 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2696 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2698 /* make all newly added notes selected */
2700 diff_add_note (copied_note, true);
2701 end_point = copied_note->end_time();
2704 paste_pos_beats += duration;
2707 /* if we pasted past the current end of the region, extend the region */
2709 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2710 nframes64_t region_end = _region->position() + _region->length() - 1;
2712 if (end_frame > region_end) {
2714 trackview.session()->begin_reversible_command (_("paste"));
2716 _region->clear_history ();
2717 _region->set_length (end_frame, this);
2718 trackview.session()->add_command (new StatefulDiffCommand (_region));
2724 struct EventNoteTimeEarlyFirstComparator {
2725 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2726 return a->note()->time() < b->note()->time();
2731 MidiRegionView::time_sort_events ()
2733 if (!_sort_needed) {
2737 EventNoteTimeEarlyFirstComparator cmp;
2740 _sort_needed = false;
2744 MidiRegionView::goto_next_note ()
2746 // nframes64_t pos = -1;
2747 bool use_next = false;
2749 if (_events.back()->selected()) {
2753 time_sort_events ();
2755 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2756 if ((*i)->selected()) {
2759 } else if (use_next) {
2761 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2766 /* use the first one */
2768 unique_select (_events.front());
2773 MidiRegionView::goto_previous_note ()
2775 // nframes64_t pos = -1;
2776 bool use_next = false;
2778 if (_events.front()->selected()) {
2782 time_sort_events ();
2784 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2785 if ((*i)->selected()) {
2788 } else if (use_next) {
2790 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2795 /* use the last one */
2797 unique_select (*(_events.rbegin()));
2801 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2803 bool had_selected = false;
2805 time_sort_events ();
2807 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2808 if ((*i)->selected()) {
2809 selected.insert ((*i)->note());
2810 had_selected = true;
2814 if (allow_all_if_none_selected && !had_selected) {
2815 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2816 selected.insert ((*i)->note());
2822 MidiRegionView::update_ghost_note (double x, double y)
2828 nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2829 trackview.editor().snap_to (f);
2830 f -= _region->position ();
2833 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2838 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2840 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2841 _ghost_note->note()->set_length (length);
2842 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2844 update_note (_ghost_note);
2846 show_verbose_canvas_cursor (_ghost_note->note ());
2850 MidiRegionView::create_ghost_note (double x, double y)
2855 boost::shared_ptr<NoteType> g (new NoteType);
2856 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2857 update_ghost_note (x, y);
2858 _ghost_note->show ();
2863 show_verbose_canvas_cursor (_ghost_note->note ());
2867 MidiRegionView::snap_changed ()
2873 create_ghost_note (_last_ghost_x, _last_ghost_y);
2877 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2880 snprintf (buf, sizeof (buf), "%s (%d)\nVel %d",
2881 Evoral::midi_note_name (n->note()).c_str(),
2883 (int) n->velocity());
2884 trackview.editor().show_verbose_canvas_cursor_with (buf);
2888 MidiRegionView::drop_down_keys ()
2890 _mouse_state = None;
2894 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
2896 double note = midi_stream_view()->y_to_note(y);
2898 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2900 cerr << "Selecting by position\n";
2902 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
2904 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
2905 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
2906 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
2907 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
2912 bool add_mrv_selection = false;
2914 if (_selection.empty()) {
2915 add_mrv_selection = true;
2918 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
2919 if (_selection.insert (*i).second) {
2920 (*i)->selected (true);
2924 if (add_mrv_selection) {
2925 PublicEditor& editor (trackview.editor());
2926 editor.get_selection().add (this);