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, true);
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), false);
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 /* yes, this steals r */
618 if (midi_view()->midi_track()->step_editing()) {
619 midi_view()->step_edit_rest ();
628 MidiRegionView::key_release (GdkEventKey* ev)
630 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
638 MidiRegionView::show_list_editor ()
641 _list_editor = new MidiListEditor (trackview.session(), midi_region());
643 _list_editor->present ();
646 /** Add a note to the model, and the view, at a canvas (click) coordinate.
647 * \param x horizontal position in pixels
648 * \param y vertical position in pixels
649 * \param length duration of the note in beats, which will be snapped to the grid
650 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
653 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
655 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
656 MidiStreamView* const view = mtv->midi_view();
658 double note = midi_stream_view()->y_to_note(y);
661 assert(note <= 127.0);
663 // Start of note in frames relative to region start
664 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
665 assert(start_frames >= 0);
668 length = frames_to_beats(
669 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
671 assert (length != 0);
674 length = frames_to_beats (beats_to_frames (length) - 1);
677 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
681 /* pick the highest selected channel, unless all channels are selected,
682 which is interpreted to mean channel 1 (zero)
685 for (uint16_t i = 0; i < 16; ++i) {
686 if (chn_mask & (1<<i)) {
696 const boost::shared_ptr<NoteType> new_note (new NoteType (channel,
697 frames_to_beats(start_frames + _region->start()), length,
698 (uint8_t)note, 0x40));
700 if (_model->contains (new_note)) {
704 view->update_note_range(new_note->note());
706 MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
708 _model->apply_command(*trackview.session(), cmd);
710 play_midi_note (new_note);
714 MidiRegionView::clear_events()
719 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
720 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
725 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
730 _pgm_changes.clear();
732 _optimization_iterator = _events.end();
737 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
740 content_connection.disconnect ();
741 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
745 if (_enable_display) {
751 MidiRegionView::start_diff_command(string name)
753 if (!_diff_command) {
754 _diff_command = _model->new_diff_command(name);
759 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
762 _diff_command->add(note);
765 _marked_for_selection.insert(note);
768 _marked_for_velocity.insert(note);
773 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
775 if (_diff_command && ev->note()) {
776 _diff_command->remove(ev->note());
781 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
782 MidiModel::DiffCommand::Property property,
786 _diff_command->change (ev->note(), property, val);
791 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
792 MidiModel::DiffCommand::Property property,
793 Evoral::MusicalTime val)
796 _diff_command->change (ev->note(), property, val);
801 MidiRegionView::apply_diff ()
805 if (!_diff_command) {
809 if ((add_or_remove = _diff_command->adds_or_removes())) {
810 // Mark all selected notes for selection when model reloads
811 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
812 _marked_for_selection.insert((*i)->note());
816 _model->apply_command(*trackview.session(), _diff_command);
818 midi_view()->midi_track()->playlist_modified();
822 _marked_for_selection.clear();
825 _marked_for_velocity.clear();
829 MidiRegionView::apply_diff_as_subcommand()
833 if (!_diff_command) {
837 if ((add_or_remove = _diff_command->adds_or_removes())) {
838 // Mark all selected notes for selection when model reloads
839 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
840 _marked_for_selection.insert((*i)->note());
844 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
846 midi_view()->midi_track()->playlist_modified();
849 _marked_for_selection.clear();
851 _marked_for_velocity.clear();
856 MidiRegionView::abort_command()
858 delete _diff_command;
864 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
866 if (_optimization_iterator != _events.end()) {
867 ++_optimization_iterator;
870 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
871 return *_optimization_iterator;
874 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
875 if ((*_optimization_iterator)->note() == note) {
876 return *_optimization_iterator;
884 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
886 MidiModel::Notes notes;
887 _model->get_notes (notes, op, val, chan_mask);
889 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
890 CanvasNoteEvent* cne = find_canvas_note (*n);
898 MidiRegionView::redisplay_model()
900 // Don't redisplay the model if we're currently recording and displaying that
906 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
910 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
914 MidiModel::ReadLock lock(_model->read_lock());
916 MidiModel::Notes& notes (_model->notes());
917 _optimization_iterator = _events.begin();
919 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
921 boost::shared_ptr<NoteType> note (*n);
922 CanvasNoteEvent* cne;
925 if (note_in_region_range (note, visible)) {
927 if ((cne = find_canvas_note (note)) != 0) {
934 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
936 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
948 add_note (note, visible);
953 if ((cne = find_canvas_note (note)) != 0) {
961 /* remove note items that are no longer valid */
963 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
964 if (!(*i)->valid ()) {
966 i = _events.erase (i);
972 _pgm_changes.clear();
976 display_program_changes();
978 _marked_for_selection.clear ();
979 _marked_for_velocity.clear ();
981 /* we may have caused _events to contain things out of order (e.g. if a note
982 moved earlier or later). we don't generally need them in time order, but
983 make a note that a sort is required for those cases that require it.
990 MidiRegionView::display_program_changes()
992 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
997 Glib::Mutex::Lock lock (control->list()->lock());
999 uint8_t channel = control->parameter().channel();
1001 for (AutomationList::const_iterator event = control->list()->begin();
1002 event != control->list()->end(); ++event) {
1003 double event_time = (*event)->when;
1004 double program_number = floor((*event)->value + 0.5);
1006 // Get current value of bank select MSB at time of the program change
1007 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1008 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1010 if (msb_control != 0) {
1011 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
1014 // Get current value of bank select LSB at time of the program change
1015 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1016 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1018 if (lsb_control != 0) {
1019 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
1022 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1024 boost::shared_ptr<MIDI::Name::Patch> patch =
1025 MIDI::Name::MidiPatchManager::instance().find_patch(
1026 _model_name, _custom_device_mode, channel, patch_key);
1028 PCEvent program_change(event_time, uint8_t(program_number), channel);
1031 add_pgm_change(program_change, patch->name());
1034 snprintf(buf, 4, "%d", int(program_number));
1035 add_pgm_change(program_change, buf);
1041 MidiRegionView::display_sysexes()
1043 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1044 Evoral::MusicalTime time = (*i)->time();
1049 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1050 str << int((*i)->buffer()[b]);
1051 if (b != (*i)->size() -1) {
1055 string text = str.str();
1057 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1059 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1061 double height = midi_stream_view()->contents_height();
1063 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1064 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1066 // Show unless program change is beyond the region bounds
1067 if (time - _region->start() >= _region->length() || time < _region->start()) {
1073 _sys_exes.push_back(sysex);
1078 MidiRegionView::~MidiRegionView ()
1080 in_destructor = true;
1082 trackview.editor().hide_verbose_canvas_cursor ();
1084 note_delete_connection.disconnect ();
1086 delete _list_editor;
1088 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1090 if (_active_notes) {
1097 delete _diff_command;
1101 MidiRegionView::region_resized (const PropertyChange& what_changed)
1103 RegionView::region_resized(what_changed);
1105 if (what_changed.contains (ARDOUR::Properties::position)) {
1106 set_duration(_region->length(), 0);
1107 if (_enable_display) {
1114 MidiRegionView::reset_width_dependent_items (double pixel_width)
1116 RegionView::reset_width_dependent_items(pixel_width);
1117 assert(_pixel_width == pixel_width);
1119 if (_enable_display) {
1125 MidiRegionView::set_height (double height)
1127 static const double FUDGE = 2.0;
1128 const double old_height = _height;
1129 RegionView::set_height(height);
1130 _height = height - FUDGE;
1132 apply_note_range(midi_stream_view()->lowest_note(),
1133 midi_stream_view()->highest_note(),
1134 height != old_height + FUDGE);
1137 name_pixbuf->raise_to_top();
1142 /** Apply the current note range from the stream view
1143 * by repositioning/hiding notes as necessary
1146 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1148 if (!_enable_display) {
1152 if (!force && _current_range_min == min && _current_range_max == max) {
1156 _current_range_min = min;
1157 _current_range_max = max;
1159 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1160 CanvasNoteEvent* event = *i;
1161 boost::shared_ptr<NoteType> note (event->note());
1163 if (note->note() < _current_range_min ||
1164 note->note() > _current_range_max) {
1170 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1172 const double y1 = midi_stream_view()->note_to_y(note->note());
1173 const double y2 = y1 + floor(midi_stream_view()->note_height());
1175 cnote->property_y1() = y1;
1176 cnote->property_y2() = y2;
1178 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1180 double x = trackview.editor().frame_to_pixel(
1181 beats_to_frames(note->time()) - _region->start());
1182 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1183 double y = midi_stream_view()->note_to_y(event->note()->note())
1184 + ((diamond_size-2.0) / 4.0);
1186 chit->set_height (diamond_size);
1187 chit->move (x - chit->x1(), y - chit->y1());
1194 MidiRegionView::add_ghost (TimeAxisView& tv)
1198 double unit_position = _region->position () / samples_per_unit;
1199 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1200 MidiGhostRegion* ghost;
1202 if (mtv && mtv->midi_view()) {
1203 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1204 to allow having midi notes on top of note lines and waveforms.
1206 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1208 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1211 ghost->set_height ();
1212 ghost->set_duration (_region->length() / samples_per_unit);
1213 ghosts.push_back (ghost);
1215 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1216 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1217 ghost->add_note(note);
1221 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1227 /** Begin tracking note state for successive calls to add_event
1230 MidiRegionView::begin_write()
1232 assert(!_active_notes);
1233 _active_notes = new CanvasNote*[128];
1234 for (unsigned i=0; i < 128; ++i) {
1235 _active_notes[i] = 0;
1240 /** Destroy note state for add_event
1243 MidiRegionView::end_write()
1245 delete[] _active_notes;
1247 _marked_for_selection.clear();
1248 _marked_for_velocity.clear();
1252 /** Resolve an active MIDI note (while recording).
1255 MidiRegionView::resolve_note(uint8_t note, double end_time)
1257 if (midi_view()->note_mode() != Sustained) {
1261 if (_active_notes && _active_notes[note]) {
1262 const nframes64_t end_time_frames = beats_to_frames(end_time);
1263 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1264 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1265 _active_notes[note] = 0;
1270 /** Extend active notes to rightmost edge of region (if length is changed)
1273 MidiRegionView::extend_active_notes()
1275 if (!_active_notes) {
1279 for (unsigned i=0; i < 128; ++i) {
1280 if (_active_notes[i]) {
1281 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1287 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1289 if (no_sound_notes || !trackview.editor().sound_notes()) {
1293 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1296 route_ui->midi_track()->write_immediate_event(
1297 note->on_event().size(), note->on_event().buffer());
1299 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1300 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1301 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1302 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1303 note_length_ms, G_PRIORITY_DEFAULT);
1307 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1309 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1312 route_ui->midi_track()->write_immediate_event(
1313 note->off_event().size(), note->off_event().buffer());
1319 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1321 const nframes64_t note_start_frames = beats_to_frames(note->time());
1323 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1324 (note_start_frames < _region->start());
1326 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1327 (note->note() <= midi_stream_view()->highest_note());
1333 MidiRegionView::update_note (CanvasNote* ev)
1335 boost::shared_ptr<NoteType> note = ev->note();
1337 const nframes64_t note_start_frames = beats_to_frames(note->time());
1338 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1340 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1341 const double y1 = midi_stream_view()->note_to_y(note->note());
1342 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1344 ev->property_x1() = x;
1345 ev->property_y1() = y1;
1346 if (note->length() > 0) {
1347 ev->property_x2() = note_endpixel;
1349 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1351 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1353 if (note->length() == 0) {
1354 if (_active_notes) {
1355 assert(note->note() < 128);
1356 // If this note is already active there's a stuck note,
1357 // finish the old note rectangle
1358 if (_active_notes[note->note()]) {
1359 CanvasNote* const old_rect = _active_notes[note->note()];
1360 boost::shared_ptr<NoteType> old_note = old_rect->note();
1361 old_rect->property_x2() = x;
1362 old_rect->property_outline_what() = (guint32) 0xF;
1364 _active_notes[note->note()] = ev;
1366 /* outline all but right edge */
1367 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1369 /* outline all edges */
1370 ev->property_outline_what() = (guint32) 0xF;
1375 MidiRegionView::update_hit (CanvasHit* ev)
1377 boost::shared_ptr<NoteType> note = ev->note();
1379 const nframes64_t note_start_frames = beats_to_frames(note->time());
1380 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1381 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1382 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1387 /** Add a MIDI note to the view (with length).
1389 * If in sustained mode, notes with length 0 will be considered active
1390 * notes, and resolve_note should be called when the corresponding note off
1391 * event arrives, to properly display the note.
1394 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1396 CanvasNoteEvent* event = 0;
1398 assert(note->time() >= 0);
1399 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1401 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1403 if (midi_view()->note_mode() == Sustained) {
1405 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1407 update_note (ev_rect);
1411 MidiGhostRegion* gr;
1413 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1414 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1415 gr->add_note(ev_rect);
1419 } else if (midi_view()->note_mode() == Percussive) {
1421 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1423 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1425 update_hit (ev_diamond);
1434 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1435 note_selected(event, true);
1438 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1439 event->show_velocity();
1441 event->on_channel_selection_change(_last_channel_selection);
1442 _events.push_back(event);
1453 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1454 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1456 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1458 start_diff_command (_("step add"));
1459 diff_add_note (new_note, true, false);
1462 /* potentially extend region to hold new note */
1464 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1465 nframes64_t region_end = _region->position() + _region->length() - 1;
1467 if (end_frame > region_end) {
1468 _region->set_length (end_frame, this);
1475 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1477 assert(program.time >= 0);
1479 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1480 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1482 double height = midi_stream_view()->contents_height();
1484 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1485 new CanvasProgramChange(*this, *group,
1490 _custom_device_mode,
1491 program.time, program.channel, program.value));
1493 // Show unless program change is beyond the region bounds
1494 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1500 _pgm_changes.push_back(pgm_change);
1504 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1506 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1507 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1508 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1510 if (msb_control != 0) {
1511 msb = int(msb_control->get_float(true, time));
1512 cerr << "got msb " << msb;
1515 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1516 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1518 if (lsb_control != 0) {
1519 lsb = lsb_control->get_float(true, time);
1520 cerr << " got lsb " << lsb;
1523 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1524 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1525 float program_number = -1.0;
1526 if (program_control != 0) {
1527 program_number = program_control->get_float(true, time);
1528 cerr << " got program " << program_number << endl;
1531 key.msb = (int) floor(msb + 0.5);
1532 key.lsb = (int) floor(lsb + 0.5);
1533 key.program_number = (int) floor(program_number + 0.5);
1534 assert(key.is_sane());
1539 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1541 // TODO: Get the real event here and alter them at the original times
1542 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1543 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1544 if (msb_control != 0) {
1545 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1548 // TODO: Get the real event here and alter them at the original times
1549 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1550 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1551 if (lsb_control != 0) {
1552 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1555 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1556 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1558 assert(program_control != 0);
1559 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1565 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1567 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1568 alter_program_change(program_change_event, new_patch);
1572 MidiRegionView::previous_program(CanvasProgramChange& program)
1574 MIDI::Name::PatchPrimaryKey key;
1575 get_patch_key_at(program.event_time(), program.channel(), key);
1577 boost::shared_ptr<MIDI::Name::Patch> patch =
1578 MIDI::Name::MidiPatchManager::instance().previous_patch(
1580 _custom_device_mode,
1584 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1586 alter_program_change(program_change_event, patch->patch_primary_key());
1591 MidiRegionView::next_program(CanvasProgramChange& program)
1593 MIDI::Name::PatchPrimaryKey key;
1594 get_patch_key_at(program.event_time(), program.channel(), key);
1596 boost::shared_ptr<MIDI::Name::Patch> patch =
1597 MIDI::Name::MidiPatchManager::instance().next_patch(
1599 _custom_device_mode,
1603 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1605 alter_program_change(program_change_event, patch->patch_primary_key());
1610 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1612 if (_selection.empty()) {
1616 if (_selection.erase (cne) > 0) {
1617 cerr << "Erased a CNE from selection\n";
1622 MidiRegionView::delete_selection()
1624 if (_selection.empty()) {
1628 start_diff_command (_("delete selection"));
1630 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1631 if ((*i)->selected()) {
1632 _diff_command->remove((*i)->note());
1642 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1644 start_diff_command (_("delete note"));
1645 _diff_command->remove (n);
1648 trackview.editor().hide_verbose_canvas_cursor ();
1652 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1654 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1655 if ((*i)->selected() && (*i) != ev) {
1656 (*i)->set_selected(false);
1657 (*i)->hide_velocity();
1665 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1667 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1670 Selection::iterator tmp = i;
1673 (*i)->set_selected (false);
1674 _selection.erase (i);
1683 /* don't bother with removing this regionview from the editor selection,
1684 since we're about to add another note, and thus put/keep this
1685 regionview in the editor selection.
1688 if (!ev->selected()) {
1689 add_to_selection (ev);
1694 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1696 uint8_t low_note = 127;
1697 uint8_t high_note = 0;
1698 MidiModel::Notes& notes (_model->notes());
1699 _optimization_iterator = _events.begin();
1705 if (extend && _selection.empty()) {
1711 /* scan existing selection to get note range */
1713 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1714 if ((*i)->note()->note() < low_note) {
1715 low_note = (*i)->note()->note();
1717 if ((*i)->note()->note() > high_note) {
1718 high_note = (*i)->note()->note();
1722 low_note = min (low_note, notenum);
1723 high_note = max (high_note, notenum);
1726 no_sound_notes = true;
1728 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1730 boost::shared_ptr<NoteType> note (*n);
1731 CanvasNoteEvent* cne;
1732 bool select = false;
1734 if (((1 << note->channel()) & channel_mask) != 0) {
1736 if ((note->note() >= low_note && note->note() <= high_note)) {
1739 } else if (note->note() == notenum) {
1745 if ((cne = find_canvas_note (note)) != 0) {
1746 // extend is false because we've taken care of it,
1747 // since it extends by time range, not pitch.
1748 note_selected (cne, add, false);
1752 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1756 no_sound_notes = false;
1760 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1762 MidiModel::Notes& notes (_model->notes());
1763 _optimization_iterator = _events.begin();
1765 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1767 boost::shared_ptr<NoteType> note (*n);
1768 CanvasNoteEvent* cne;
1770 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1771 if ((cne = find_canvas_note (note)) != 0) {
1772 if (cne->selected()) {
1773 note_deselected (cne);
1775 note_selected (cne, true, false);
1783 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1786 clear_selection_except(ev);
1791 if (!ev->selected()) {
1792 add_to_selection (ev);
1796 /* find end of latest note selected, select all between that and the start of "ev" */
1798 Evoral::MusicalTime earliest = DBL_MAX;
1799 Evoral::MusicalTime latest = 0;
1801 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1802 if ((*i)->note()->end_time() > latest) {
1803 latest = (*i)->note()->end_time();
1805 if ((*i)->note()->time() < earliest) {
1806 earliest = (*i)->note()->time();
1810 if (ev->note()->end_time() > latest) {
1811 latest = ev->note()->end_time();
1814 if (ev->note()->time() < earliest) {
1815 earliest = ev->note()->time();
1818 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1820 /* find notes entirely within OR spanning the earliest..latest range */
1822 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1823 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1824 add_to_selection (*i);
1828 /* if events were guaranteed to be time sorted, we could do this.
1829 but as of sept 10th 2009, they no longer are.
1832 if ((*i)->note()->time() > latest) {
1841 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1843 remove_from_selection (ev);
1847 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1857 // TODO: Make this faster by storing the last updated selection rect, and only
1858 // adjusting things that are in the area that appears/disappeared.
1859 // We probably need a tree to be able to find events in O(log(n)) time.
1861 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1863 /* check if any corner of the note is inside the rect
1866 1) this is computing "touched by", not "contained by" the rect.
1867 2) this does not require that events be sorted in time.
1870 const double ix1 = (*i)->x1();
1871 const double ix2 = (*i)->x2();
1872 const double iy1 = (*i)->y1();
1873 const double iy2 = (*i)->y2();
1875 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1876 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1877 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1878 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1881 if (!(*i)->selected()) {
1882 add_to_selection (*i);
1884 } else if ((*i)->selected()) {
1885 // Not inside rectangle
1886 remove_from_selection (*i);
1892 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1894 Selection::iterator i = _selection.find (ev);
1896 if (i != _selection.end()) {
1897 _selection.erase (i);
1900 ev->set_selected (false);
1901 ev->hide_velocity ();
1903 if (_selection.empty()) {
1904 PublicEditor& editor (trackview.editor());
1905 editor.get_selection().remove (this);
1910 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1912 bool add_mrv_selection = false;
1914 if (_selection.empty()) {
1915 add_mrv_selection = true;
1918 if (_selection.insert (ev).second) {
1919 ev->set_selected (true);
1920 play_midi_note ((ev)->note());
1923 if (add_mrv_selection) {
1924 PublicEditor& editor (trackview.editor());
1925 editor.get_selection().add (this);
1930 MidiRegionView::move_selection(double dx, double dy)
1932 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1933 (*i)->move_event(dx, dy);
1938 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1940 assert (!_selection.empty());
1942 uint8_t lowest_note_in_selection = 127;
1943 uint8_t highest_note_in_selection = 0;
1944 uint8_t highest_note_difference = 0;
1946 // find highest and lowest notes first
1948 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1949 uint8_t pitch = (*i)->note()->note();
1950 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1951 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1955 cerr << "dnote: " << (int) dnote << endl;
1956 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1957 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1958 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1959 << int(highest_note_in_selection) << endl;
1960 cerr << "selection size: " << _selection.size() << endl;
1961 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1964 // Make sure the note pitch does not exceed the MIDI standard range
1965 if (highest_note_in_selection + dnote > 127) {
1966 highest_note_difference = highest_note_in_selection - 127;
1969 start_diff_command(_("move notes"));
1971 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1973 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1976 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1978 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1981 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1987 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1989 uint8_t original_pitch = (*i)->note()->note();
1990 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1992 // keep notes in standard midi range
1993 clamp_to_0_127(new_pitch);
1995 // keep original pitch if note is dragged outside valid midi range
1996 if ((original_pitch != 0 && new_pitch == 0)
1997 || (original_pitch != 127 && new_pitch == 127)) {
1998 new_pitch = original_pitch;
2001 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2002 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2004 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
2009 // care about notes being moved beyond the upper/lower bounds on the canvas
2010 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2011 highest_note_in_selection > midi_stream_view()->highest_note()) {
2012 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2017 MidiRegionView::snap_pixel_to_frame(double x)
2019 PublicEditor& editor = trackview.editor();
2020 // x is region relative, convert it to global absolute frames
2021 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
2022 editor.snap_to(frame);
2023 return frame - _region->position(); // convert back to region relative
2027 MidiRegionView::snap_frame_to_frame(nframes64_t x)
2029 PublicEditor& editor = trackview.editor();
2030 // x is region relative, convert it to global absolute frames
2031 nframes64_t frame = x + _region->position();
2032 editor.snap_to(frame);
2033 return frame - _region->position(); // convert back to region relative
2037 MidiRegionView::snap_to_pixel(double x)
2039 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2043 MidiRegionView::get_position_pixels()
2045 nframes64_t region_frame = get_position();
2046 return trackview.editor().frame_to_pixel(region_frame);
2050 MidiRegionView::get_end_position_pixels()
2052 nframes64_t frame = get_position() + get_duration ();
2053 return trackview.editor().frame_to_pixel(frame);
2057 MidiRegionView::beats_to_frames(double beats) const
2059 return _time_converter.to(beats);
2063 MidiRegionView::frames_to_beats(nframes64_t frames) const
2065 return _time_converter.from(frames);
2069 MidiRegionView::begin_resizing (bool /*at_front*/)
2071 _resize_data.clear();
2073 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2074 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2076 // only insert CanvasNotes into the map
2078 NoteResizeData *resize_data = new NoteResizeData();
2079 resize_data->canvas_note = note;
2081 // create a new SimpleRect from the note which will be the resize preview
2082 SimpleRect *resize_rect = new SimpleRect(
2083 *group, note->x1(), note->y1(), note->x2(), note->y2());
2085 // calculate the colors: get the color settings
2086 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2087 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2090 // make the resize preview notes more transparent and bright
2091 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2093 // calculate color based on note velocity
2094 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2095 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2099 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2100 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2102 resize_data->resize_rect = resize_rect;
2103 _resize_data.push_back(resize_data);
2108 /** Update resizing notes while user drags.
2109 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2110 * @param at_front which end of the note (true == note on, false == note off)
2111 * @param delta_x change in mouse position since the start of the drag
2112 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2113 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2114 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2115 * as the \a primary note.
2118 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2120 bool cursor_set = false;
2122 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2123 SimpleRect* resize_rect = (*i)->resize_rect;
2124 CanvasNote* canvas_note = (*i)->canvas_note;
2129 current_x = canvas_note->x1() + delta_x;
2131 current_x = primary->x1() + delta_x;
2135 current_x = canvas_note->x2() + delta_x;
2137 current_x = primary->x2() + delta_x;
2142 resize_rect->property_x1() = snap_to_pixel(current_x);
2143 resize_rect->property_x2() = canvas_note->x2();
2145 resize_rect->property_x2() = snap_to_pixel(current_x);
2146 resize_rect->property_x1() = canvas_note->x1();
2152 beats = snap_pixel_to_frame (current_x);
2153 beats = frames_to_beats (beats);
2158 if (beats < canvas_note->note()->end_time()) {
2159 len = canvas_note->note()->time() - beats;
2160 len += canvas_note->note()->length();
2165 if (beats >= canvas_note->note()->end_time()) {
2166 len = beats - canvas_note->note()->time();
2173 snprintf (buf, sizeof (buf), "%.3g beats", len);
2174 trackview.editor().show_verbose_canvas_cursor_with (buf);
2183 /** Finish resizing notes when the user releases the mouse button.
2184 * Parameters the same as for \a update_resizing().
2187 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2189 start_diff_command(_("resize notes"));
2191 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2192 CanvasNote* canvas_note = (*i)->canvas_note;
2193 SimpleRect* resize_rect = (*i)->resize_rect;
2198 current_x = canvas_note->x1() + delta_x;
2200 current_x = primary->x1() + delta_x;
2204 current_x = canvas_note->x2() + delta_x;
2206 current_x = primary->x2() + delta_x;
2210 current_x = snap_pixel_to_frame (current_x);
2211 current_x = frames_to_beats (current_x);
2213 if (at_front && current_x < canvas_note->note()->end_time()) {
2214 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2216 double len = canvas_note->note()->time() - current_x;
2217 len += canvas_note->note()->length();
2220 /* XXX convert to beats */
2221 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2226 double len = current_x - canvas_note->note()->time();
2229 /* XXX convert to beats */
2230 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2238 _resize_data.clear();
2243 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2245 uint8_t new_velocity;
2248 new_velocity = event->note()->velocity() + velocity;
2249 clamp_to_0_127(new_velocity);
2251 new_velocity = velocity;
2254 event->set_selected (event->selected()); // change color
2256 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2260 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2265 new_note = event->note()->note() + note;
2270 clamp_to_0_127 (new_note);
2271 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2275 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2277 bool change_start = false;
2278 bool change_length = false;
2279 Evoral::MusicalTime new_start = 0;
2280 Evoral::MusicalTime new_length = 0;
2282 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2284 front_delta: if positive - move the start of the note later in time (shortening it)
2285 if negative - move the start of the note earlier in time (lengthening it)
2287 end_delta: if positive - move the end of the note later in time (lengthening it)
2288 if negative - move the end of the note earlier in time (shortening it)
2292 if (front_delta < 0) {
2294 if (event->note()->time() < -front_delta) {
2297 new_start = event->note()->time() + front_delta; // moves earlier
2300 /* start moved toward zero, so move the end point out to where it used to be.
2301 Note that front_delta is negative, so this increases the length.
2304 new_length = event->note()->length() - front_delta;
2305 change_start = true;
2306 change_length = true;
2310 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2312 if (new_pos < event->note()->end_time()) {
2313 new_start = event->note()->time() + front_delta;
2314 /* start moved toward the end, so move the end point back to where it used to be */
2315 new_length = event->note()->length() - front_delta;
2316 change_start = true;
2317 change_length = true;
2324 bool can_change = true;
2325 if (end_delta < 0) {
2326 if (event->note()->length() < -end_delta) {
2332 new_length = event->note()->length() + end_delta;
2333 change_length = true;
2338 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2341 if (change_length) {
2342 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2347 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2349 Evoral::MusicalTime new_time;
2353 if (event->note()->time() < -delta) {
2356 new_time = event->note()->time() + delta;
2359 new_time = event->note()->time() + delta;
2365 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2369 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2373 if (_selection.empty()) {
2388 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2389 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2395 start_diff_command(_("change velocities"));
2397 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2398 Selection::iterator next = i;
2400 change_note_velocity (*i, delta, true);
2406 if (!_selection.empty()) {
2408 snprintf (buf, sizeof (buf), "Vel %d",
2409 (int) (*_selection.begin())->note()->velocity());
2410 trackview.editor().show_verbose_canvas_cursor_with (buf);
2416 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2418 if (_selection.empty()) {
2435 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2437 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2441 if ((int8_t) (*i)->note()->note() + delta > 127) {
2448 start_diff_command (_("transpose"));
2450 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2451 Selection::iterator next = i;
2453 change_note_note (*i, delta, true);
2461 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2463 Evoral::MusicalTime delta;
2468 /* grab the current grid distance */
2470 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2472 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2473 cerr << "Grid type not available as beats - TO BE FIXED\n";
2482 start_diff_command (_("change note lengths"));
2484 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2485 Selection::iterator next = i;
2488 /* note the negation of the delta for start */
2490 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2499 MidiRegionView::nudge_notes (bool forward)
2501 if (_selection.empty()) {
2505 /* pick a note as the point along the timeline to get the nudge distance.
2506 its not necessarily the earliest note, so we may want to pull the notes out
2507 into a vector and sort before using the first one.
2510 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2512 nframes64_t distance;
2514 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2516 /* grid is off - use nudge distance */
2518 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2524 nframes64_t next_pos = ref_point;
2527 /* XXX need check on max_frames, but that needs max_frames64 or something */
2530 if (next_pos == 0) {
2536 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2537 distance = ref_point - next_pos;
2540 if (distance == 0) {
2544 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2550 start_diff_command (_("nudge"));
2552 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2553 Selection::iterator next = i;
2555 change_note_time (*i, delta, true);
2563 MidiRegionView::change_channel(uint8_t channel)
2565 start_diff_command(_("change channel"));
2566 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2567 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2575 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2577 if (_mouse_state == SelectTouchDragging) {
2578 note_selected (ev, true);
2581 show_verbose_canvas_cursor (ev->note ());
2585 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2587 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2588 (*i)->hide_velocity ();
2591 trackview.editor().hide_verbose_canvas_cursor ();
2595 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2597 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2599 display_model(msrc->model());
2603 MidiRegionView::set_frame_color()
2606 if (_selected && should_show_selection) {
2607 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2609 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2615 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2619 case FilterChannels:
2620 _force_channel = -1;
2623 _force_channel = mask;
2624 mask = 0xFFFF; // Show all notes as active (below)
2627 // Update notes for selection
2628 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2629 (*i)->on_channel_selection_change(mask);
2632 _last_channel_selection = mask;
2636 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2638 _model_name = model;
2639 _custom_device_mode = custom_device_mode;
2644 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2646 if (_selection.empty()) {
2650 PublicEditor& editor (trackview.editor());
2655 editor.get_cut_buffer().add (selection_as_cut_buffer());
2663 start_diff_command();
2665 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2671 diff_remove_note (*i);
2681 MidiRegionView::selection_as_cut_buffer () const
2685 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2686 NoteType* n = (*i)->note().get();
2687 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2690 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2697 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2703 start_diff_command (_("paste"));
2705 Evoral::MusicalTime beat_delta;
2706 Evoral::MusicalTime paste_pos_beats;
2707 Evoral::MusicalTime duration;
2708 Evoral::MusicalTime end_point = 0;
2710 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2711 paste_pos_beats = frames_to_beats (pos - _region->position());
2712 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2713 paste_pos_beats = 0;
2715 _selection.clear ();
2717 for (int n = 0; n < (int) times; ++n) {
2719 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2721 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2722 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2724 /* make all newly added notes selected */
2726 diff_add_note (copied_note, true);
2727 end_point = copied_note->end_time();
2730 paste_pos_beats += duration;
2733 /* if we pasted past the current end of the region, extend the region */
2735 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2736 nframes64_t region_end = _region->position() + _region->length() - 1;
2738 if (end_frame > region_end) {
2740 trackview.session()->begin_reversible_command (_("paste"));
2742 _region->clear_history ();
2743 _region->set_length (end_frame, this);
2744 trackview.session()->add_command (new StatefulDiffCommand (_region));
2750 struct EventNoteTimeEarlyFirstComparator {
2751 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2752 return a->note()->time() < b->note()->time();
2757 MidiRegionView::time_sort_events ()
2759 if (!_sort_needed) {
2763 EventNoteTimeEarlyFirstComparator cmp;
2766 _sort_needed = false;
2770 MidiRegionView::goto_next_note ()
2772 // nframes64_t pos = -1;
2773 bool use_next = false;
2775 if (_events.back()->selected()) {
2779 time_sort_events ();
2781 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2782 if ((*i)->selected()) {
2785 } else if (use_next) {
2787 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2792 /* use the first one */
2794 unique_select (_events.front());
2799 MidiRegionView::goto_previous_note ()
2801 // nframes64_t pos = -1;
2802 bool use_next = false;
2804 if (_events.front()->selected()) {
2808 time_sort_events ();
2810 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2811 if ((*i)->selected()) {
2814 } else if (use_next) {
2816 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2821 /* use the last one */
2823 unique_select (*(_events.rbegin()));
2827 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2829 bool had_selected = false;
2831 time_sort_events ();
2833 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2834 if ((*i)->selected()) {
2835 selected.insert ((*i)->note());
2836 had_selected = true;
2840 if (allow_all_if_none_selected && !had_selected) {
2841 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2842 selected.insert ((*i)->note());
2848 MidiRegionView::update_ghost_note (double x, double y)
2854 nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2855 trackview.editor().snap_to (f);
2856 f -= _region->position ();
2859 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2864 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2866 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2867 _ghost_note->note()->set_length (length);
2868 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2870 update_note (_ghost_note);
2872 show_verbose_canvas_cursor (_ghost_note->note ());
2876 MidiRegionView::create_ghost_note (double x, double y)
2881 boost::shared_ptr<NoteType> g (new NoteType);
2882 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2883 update_ghost_note (x, y);
2884 _ghost_note->show ();
2889 show_verbose_canvas_cursor (_ghost_note->note ());
2893 MidiRegionView::snap_changed ()
2899 create_ghost_note (_last_ghost_x, _last_ghost_y);
2903 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2906 snprintf (buf, sizeof (buf), "%s (%d)\nVel %d",
2907 Evoral::midi_note_name (n->note()).c_str(),
2909 (int) n->velocity());
2910 trackview.editor().show_verbose_canvas_cursor_with (buf);
2914 MidiRegionView::drop_down_keys ()
2916 _mouse_state = None;
2920 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
2922 double note = midi_stream_view()->y_to_note(y);
2924 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2926 cerr << "Selecting by position\n";
2928 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
2930 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
2931 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
2932 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
2933 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
2938 bool add_mrv_selection = false;
2940 if (_selection.empty()) {
2941 add_mrv_selection = true;
2944 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
2945 if (_selection.insert (*i).second) {
2946 (*i)->set_selected (true);
2950 if (add_mrv_selection) {
2951 PublicEditor& editor (trackview.editor());
2952 editor.get_selection().add (this);
2957 MidiRegionView::color_handler ()
2959 RegionView::color_handler ();
2961 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2962 (*i)->set_selected ((*i)->selected()); // will change color
2965 /* XXX probably more to do here */
2969 MidiRegionView::enable_display (bool yn)
2971 RegionView::enable_display (yn);