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();
99 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
100 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
101 TimeAxisViewItem::Visibility visibility)
102 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
104 , _last_channel_selection(0xFFFF)
105 , _model_name(string())
106 , _custom_device_mode(string())
108 , _note_group(new ArdourCanvas::Group(*parent))
114 , _sort_needed (true)
115 , _optimization_iterator (_events.end())
117 , no_sound_notes (false)
120 _note_group->raise_to_top();
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 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
183 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
187 midi_region()->midi_source(0)->load_model();
190 _model = midi_region()->midi_source(0)->model();
191 _enable_display = false;
193 RegionView::init (basic_color, false);
195 compute_colors (basic_color);
197 set_height (trackview.current_height());
200 region_sync_changed ();
201 region_resized (ARDOUR::bounds_change);
204 reset_width_dependent_items (_pixel_width);
208 _enable_display = true;
211 display_model (_model);
215 group->raise_to_top();
216 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
218 midi_view()->signal_channel_mode_changed().connect(
219 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
221 midi_view()->signal_midi_patch_settings_changed().connect(
222 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
224 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
228 MidiRegionView::canvas_event(GdkEvent* ev)
230 PublicEditor& editor (trackview.editor());
232 if (!editor.internal_editing()) {
236 static double drag_start_x, drag_start_y;
237 static double last_x, last_y;
238 double event_x, event_y;
239 nframes64_t event_frame = 0;
242 static ArdourCanvas::SimpleRect* drag_rect = 0;
244 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
245 to its items, which means that ev->type == GDK_SCROLL will never be seen
250 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
252 if (ev->scroll.direction == GDK_SCROLL_UP) {
253 change_velocities (true, fine, false);
255 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
256 change_velocities (false, fine, false);
265 /* since GTK bindings are generally activated on press, and since
266 detectable auto-repeat is the name of the game and only sends
267 repeated presses, carry out key actions at key press, not release.
270 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
271 _mouse_state = SelectTouchDragging;
274 } else if (ev->key.keyval == GDK_Escape) {
278 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
280 bool start = (ev->key.keyval == GDK_comma);
281 bool end = (ev->key.keyval == GDK_period);
282 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
283 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
285 change_note_lengths (fine, shorter, start, end);
289 } else if (ev->key.keyval == GDK_Delete) {
294 } else if (ev->key.keyval == GDK_Tab) {
296 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
297 goto_previous_note ();
303 } else if (ev->key.keyval == GDK_Up) {
305 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
306 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
308 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
309 change_velocities (true, fine, allow_smush);
311 transpose (true, fine, allow_smush);
315 } else if (ev->key.keyval == GDK_Down) {
317 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
318 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
320 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
321 change_velocities (false, fine, allow_smush);
323 transpose (false, fine, allow_smush);
327 } else if (ev->key.keyval == GDK_Left) {
332 } else if (ev->key.keyval == GDK_Right) {
337 } else if (ev->key.keyval == GDK_Control_L) {
340 } else if (ev->key.keyval == GDK_r) {
341 /* if we're not step editing, this really doesn't matter */
342 midi_view()->step_edit_rest ();
348 case GDK_KEY_RELEASE:
349 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
355 case GDK_BUTTON_PRESS:
356 last_x = ev->button.x;
357 last_y = ev->button.y;
358 group->w2i (last_x, last_y);
360 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
361 _pressed_button = ev->button.button;
362 _mouse_state = Pressed;
365 _pressed_button = ev->button.button;
368 case GDK_2BUTTON_PRESS:
371 case GDK_ENTER_NOTIFY:
373 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
374 Keyboard::magic_widget_grab_focus();
377 if (editor.current_mouse_mode() == MouseRange) {
378 create_ghost_note (ev->crossing.x, ev->crossing.y);
383 case GDK_LEAVE_NOTIFY:
385 trackview.editor().hide_verbose_canvas_cursor ();
391 case GDK_MOTION_NOTIFY:
393 event_x = ev->motion.x;
394 event_y = ev->motion.y;
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->motion.x, ev->motion.y);
407 switch (_mouse_state) {
408 case Pressed: // Maybe start a drag, if we've moved a bit
410 if (fabs (event_x - last_x) < 1 && fabs (event_y - last_y) < 1) {
411 /* no appreciable movement since the button was pressed */
416 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
417 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
418 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
421 drag_start_x = event_x;
422 drag_start_y = event_y;
424 drag_rect = new ArdourCanvas::SimpleRect(*group);
425 drag_rect->property_x1() = event_x;
426 drag_rect->property_y1() = event_y;
427 drag_rect->property_x2() = event_x;
428 drag_rect->property_y2() = event_y;
429 drag_rect->property_outline_what() = 0xFF;
430 drag_rect->property_outline_color_rgba()
431 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
432 drag_rect->property_fill_color_rgba()
433 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
435 _mouse_state = SelectRectDragging;
438 // Add note drag start
439 } else if (editor.internal_editing()) {
444 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
445 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
448 drag_start_x = event_x;
449 drag_start_y = event_y;
451 drag_rect = new ArdourCanvas::SimpleRect(*group);
452 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
454 drag_rect->property_y1() = midi_stream_view()->note_to_y(
455 midi_stream_view()->y_to_note(event_y));
456 drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
457 drag_rect->property_y2() = drag_rect->property_y1()
458 + floor(midi_stream_view()->note_height());
459 drag_rect->property_outline_what() = 0xFF;
460 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
461 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
463 _mouse_state = AddDragging;
469 case SelectRectDragging: // Select drag motion
470 case AddDragging: // Add note drag motion
471 if (ev->motion.is_hint) {
474 GdkModifierType state;
475 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
480 if (_mouse_state == AddDragging)
481 event_x = trackview.editor().frame_to_pixel(event_frame);
484 if (event_x > drag_start_x)
485 drag_rect->property_x2() = event_x;
487 drag_rect->property_x1() = event_x;
490 if (drag_rect && _mouse_state == SelectRectDragging) {
491 if (event_y > drag_start_y)
492 drag_rect->property_y2() = event_y;
494 drag_rect->property_y1() = event_y;
496 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
502 case SelectTouchDragging:
511 case GDK_BUTTON_RELEASE:
512 event_x = ev->motion.x;
513 event_y = ev->motion.y;
514 group->w2i(event_x, event_y);
515 group->ungrab(ev->button.time);
516 event_frame = trackview.editor().pixel_to_frame(event_x);
518 if (ev->button.button == 3) {
520 } else if (_pressed_button != 1) {
524 switch (_mouse_state) {
525 case Pressed: // Clicked
526 switch (editor.current_mouse_mode()) {
534 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
539 create_note_at (event_x, event_y, beats);
547 case SelectRectDragging: // Select drag done
552 case AddDragging: // Add drag done
554 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
555 const double x = drag_rect->property_x1();
556 const double length = trackview.editor().pixel_to_frame(
557 drag_rect->property_x2() - drag_rect->property_x1());
559 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
565 create_ghost_note (ev->button.x, ev->button.y);
576 MidiRegionView::show_list_editor ()
579 _list_editor = new MidiListEditor (trackview.session(), midi_region());
581 _list_editor->present ();
584 /** Add a note to the model, and the view, at a canvas (click) coordinate.
585 * \param x horizontal position in pixels
586 * \param y vertical position in pixels
587 * \param length duration of the note in beats */
589 MidiRegionView::create_note_at(double x, double y, double length)
591 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
592 MidiStreamView* const view = mtv->midi_view();
594 double note = midi_stream_view()->y_to_note(y);
597 assert(note <= 127.0);
599 // Start of note in frames relative to region start
600 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
601 assert(start_frames >= 0);
604 length = frames_to_beats(
605 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
607 assert (length != 0);
609 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
610 frames_to_beats(start_frames + _region->start()), length,
611 (uint8_t)note, 0x40));
613 if (_model->contains (new_note)) {
617 view->update_note_range(new_note->note());
619 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
621 _model->apply_command(*trackview.session(), cmd);
623 play_midi_note (new_note);
627 MidiRegionView::clear_events()
632 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
633 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
638 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
643 _pgm_changes.clear();
645 _optimization_iterator = _events.end();
650 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
653 content_connection.disconnect ();
654 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
658 if (_enable_display) {
665 MidiRegionView::start_delta_command(string name)
667 if (!_delta_command) {
668 _delta_command = _model->new_delta_command(name);
673 MidiRegionView::start_diff_command(string name)
675 if (!_diff_command) {
676 _diff_command = _model->new_diff_command(name);
681 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
683 if (_delta_command) {
684 _delta_command->add(note);
687 _marked_for_selection.insert(note);
690 _marked_for_velocity.insert(note);
695 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
697 if (_delta_command && ev->note()) {
698 _delta_command->remove(ev->note());
703 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
704 MidiModel::DiffCommand::Property property,
708 _diff_command->change (ev->note(), property, val);
713 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
714 MidiModel::DiffCommand::Property property,
715 Evoral::MusicalTime val)
718 _diff_command->change (ev->note(), property, val);
723 MidiRegionView::apply_delta()
725 if (!_delta_command) {
729 // Mark all selected notes for selection when model reloads
730 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
731 _marked_for_selection.insert((*i)->note());
734 _model->apply_command(*trackview.session(), _delta_command);
736 midi_view()->midi_track()->playlist_modified();
738 _marked_for_selection.clear();
739 _marked_for_velocity.clear();
743 MidiRegionView::apply_diff ()
745 if (!_diff_command) {
749 _model->apply_command(*trackview.session(), _diff_command);
751 midi_view()->midi_track()->playlist_modified();
753 _marked_for_velocity.clear();
757 MidiRegionView::apply_delta_as_subcommand()
759 if (!_delta_command) {
763 // Mark all selected notes for selection when model reloads
764 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
765 _marked_for_selection.insert((*i)->note());
768 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
770 midi_view()->midi_track()->playlist_modified();
772 _marked_for_selection.clear();
773 _marked_for_velocity.clear();
777 MidiRegionView::apply_diff_as_subcommand()
779 if (!_diff_command) {
783 // Mark all selected notes for selection when model reloads
784 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
785 _marked_for_selection.insert((*i)->note());
788 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
790 midi_view()->midi_track()->playlist_modified();
792 _marked_for_selection.clear();
793 _marked_for_velocity.clear();
797 MidiRegionView::abort_command()
799 delete _delta_command;
801 delete _diff_command;
807 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
809 if (_optimization_iterator != _events.end()) {
810 ++_optimization_iterator;
813 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
814 return *_optimization_iterator;
817 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
818 if ((*_optimization_iterator)->note() == note) {
819 return *_optimization_iterator;
827 MidiRegionView::redisplay_model()
829 // Don't redisplay the model if we're currently recording and displaying that
835 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
839 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
843 MidiModel::ReadLock lock(_model->read_lock());
845 MidiModel::Notes& notes (_model->notes());
846 _optimization_iterator = _events.begin();
848 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
850 boost::shared_ptr<NoteType> note (*n);
851 CanvasNoteEvent* cne;
854 if (note_in_region_range (note, visible)) {
856 if ((cne = find_canvas_note (note)) != 0) {
863 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
865 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
877 add_note (note, visible);
882 if ((cne = find_canvas_note (note)) != 0) {
890 /* remove note items that are no longer valid */
892 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
893 if (!(*i)->valid ()) {
895 i = _events.erase (i);
902 display_program_changes();
904 _marked_for_selection.clear ();
905 _marked_for_velocity.clear ();
907 /* we may have caused _events to contain things out of order (e.g. if a note
908 moved earlier or later). we don't generally need them in time order, but
909 make a note that a sort is required for those cases that require it.
916 MidiRegionView::display_program_changes()
918 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
923 Glib::Mutex::Lock lock (control->list()->lock());
925 uint8_t channel = control->parameter().channel();
927 for (AutomationList::const_iterator event = control->list()->begin();
928 event != control->list()->end(); ++event) {
929 double event_time = (*event)->when;
930 double program_number = floor((*event)->value + 0.5);
932 // Get current value of bank select MSB at time of the program change
933 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
934 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
936 if (msb_control != 0) {
937 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
940 // Get current value of bank select LSB at time of the program change
941 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
942 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
944 if (lsb_control != 0) {
945 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
948 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
950 boost::shared_ptr<MIDI::Name::Patch> patch =
951 MIDI::Name::MidiPatchManager::instance().find_patch(
952 _model_name, _custom_device_mode, channel, patch_key);
954 PCEvent program_change(event_time, uint8_t(program_number), channel);
957 add_pgm_change(program_change, patch->name());
960 snprintf(buf, 4, "%d", int(program_number));
961 add_pgm_change(program_change, buf);
967 MidiRegionView::display_sysexes()
969 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
970 Evoral::MusicalTime time = (*i)->time();
975 for (uint32_t b = 0; b < (*i)->size(); ++b) {
976 str << int((*i)->buffer()[b]);
977 if (b != (*i)->size() -1) {
981 string text = str.str();
983 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
985 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
987 double height = midi_stream_view()->contents_height();
989 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
990 new CanvasSysEx(*this, *group, text, height, x, 1.0));
992 // Show unless program change is beyond the region bounds
993 if (time - _region->start() >= _region->length() || time < _region->start()) {
999 _sys_exes.push_back(sysex);
1004 MidiRegionView::~MidiRegionView ()
1006 in_destructor = true;
1008 note_delete_connection.disconnect ();
1010 delete _list_editor;
1012 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1014 if (_active_notes) {
1021 delete _delta_command;
1025 MidiRegionView::region_resized (const PropertyChange& what_changed)
1027 RegionView::region_resized(what_changed);
1029 if (what_changed.contains (ARDOUR::Properties::position)) {
1030 set_duration(_region->length(), 0);
1031 if (_enable_display) {
1038 MidiRegionView::reset_width_dependent_items (double pixel_width)
1040 RegionView::reset_width_dependent_items(pixel_width);
1041 assert(_pixel_width == pixel_width);
1043 if (_enable_display) {
1049 MidiRegionView::set_height (double height)
1051 static const double FUDGE = 2.0;
1052 const double old_height = _height;
1053 RegionView::set_height(height);
1054 _height = height - FUDGE;
1056 apply_note_range(midi_stream_view()->lowest_note(),
1057 midi_stream_view()->highest_note(),
1058 height != old_height + FUDGE);
1061 name_pixbuf->raise_to_top();
1066 /** Apply the current note range from the stream view
1067 * by repositioning/hiding notes as necessary
1070 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1072 if (!_enable_display) {
1076 if (!force && _current_range_min == min && _current_range_max == max) {
1080 _current_range_min = min;
1081 _current_range_max = max;
1083 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1084 CanvasNoteEvent* event = *i;
1085 boost::shared_ptr<NoteType> note (event->note());
1087 if (note->note() < _current_range_min ||
1088 note->note() > _current_range_max) {
1094 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1096 const double y1 = midi_stream_view()->note_to_y(note->note());
1097 const double y2 = y1 + floor(midi_stream_view()->note_height());
1099 cnote->property_y1() = y1;
1100 cnote->property_y2() = y2;
1102 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1104 double x = trackview.editor().frame_to_pixel(
1105 beats_to_frames(note->time()) - _region->start());
1106 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1107 double y = midi_stream_view()->note_to_y(event->note()->note())
1108 + ((diamond_size-2.0) / 4.0);
1110 chit->set_height (diamond_size);
1111 chit->move (x - chit->x1(), y - chit->y1());
1118 MidiRegionView::add_ghost (TimeAxisView& tv)
1122 double unit_position = _region->position () / samples_per_unit;
1123 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1124 MidiGhostRegion* ghost;
1126 if (mtv && mtv->midi_view()) {
1127 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1128 to allow having midi notes on top of note lines and waveforms.
1130 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1132 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1135 ghost->set_height ();
1136 ghost->set_duration (_region->length() / samples_per_unit);
1137 ghosts.push_back (ghost);
1139 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1140 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1141 ghost->add_note(note);
1145 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1151 /** Begin tracking note state for successive calls to add_event
1154 MidiRegionView::begin_write()
1156 assert(!_active_notes);
1157 _active_notes = new CanvasNote*[128];
1158 for (unsigned i=0; i < 128; ++i) {
1159 _active_notes[i] = 0;
1164 /** Destroy note state for add_event
1167 MidiRegionView::end_write()
1169 delete[] _active_notes;
1171 _marked_for_selection.clear();
1172 _marked_for_velocity.clear();
1176 /** Resolve an active MIDI note (while recording).
1179 MidiRegionView::resolve_note(uint8_t note, double end_time)
1181 if (midi_view()->note_mode() != Sustained) {
1185 if (_active_notes && _active_notes[note]) {
1186 const nframes64_t end_time_frames = beats_to_frames(end_time);
1187 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1188 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1189 _active_notes[note] = 0;
1194 /** Extend active notes to rightmost edge of region (if length is changed)
1197 MidiRegionView::extend_active_notes()
1199 if (!_active_notes) {
1203 for (unsigned i=0; i < 128; ++i) {
1204 if (_active_notes[i]) {
1205 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1211 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1213 if (no_sound_notes || !trackview.editor().sound_notes()) {
1217 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1220 route_ui->midi_track()->write_immediate_event(
1221 note->on_event().size(), note->on_event().buffer());
1223 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1224 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1225 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1226 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1227 note_length_ms, G_PRIORITY_DEFAULT);
1231 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1233 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1236 route_ui->midi_track()->write_immediate_event(
1237 note->off_event().size(), note->off_event().buffer());
1243 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1245 const nframes64_t note_start_frames = beats_to_frames(note->time());
1247 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1248 (note_start_frames < _region->start());
1250 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1251 (note->note() <= midi_stream_view()->highest_note());
1257 MidiRegionView::update_note (CanvasNote* ev)
1259 boost::shared_ptr<NoteType> note = ev->note();
1261 const nframes64_t note_start_frames = beats_to_frames(note->time());
1262 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1264 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1265 const double y1 = midi_stream_view()->note_to_y(note->note());
1266 const double note_endpixel =
1267 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1269 ev->property_x1() = x;
1270 ev->property_y1() = y1;
1271 if (note->length() > 0) {
1272 ev->property_x2() = note_endpixel;
1274 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1276 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1278 if (note->length() == 0) {
1279 if (_active_notes) {
1280 assert(note->note() < 128);
1281 // If this note is already active there's a stuck note,
1282 // finish the old note rectangle
1283 if (_active_notes[note->note()]) {
1284 CanvasNote* const old_rect = _active_notes[note->note()];
1285 boost::shared_ptr<NoteType> old_note = old_rect->note();
1286 old_rect->property_x2() = x;
1287 old_rect->property_outline_what() = (guint32) 0xF;
1289 _active_notes[note->note()] = ev;
1291 /* outline all but right edge */
1292 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1294 /* outline all edges */
1295 ev->property_outline_what() = (guint32) 0xF;
1300 MidiRegionView::update_hit (CanvasHit* ev)
1302 boost::shared_ptr<NoteType> note = ev->note();
1304 const nframes64_t note_start_frames = beats_to_frames(note->time());
1305 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1306 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1307 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1312 /** Add a MIDI note to the view (with length).
1314 * If in sustained mode, notes with length 0 will be considered active
1315 * notes, and resolve_note should be called when the corresponding note off
1316 * event arrives, to properly display the note.
1319 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1321 CanvasNoteEvent* event = 0;
1323 assert(note->time() >= 0);
1324 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1326 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1328 if (midi_view()->note_mode() == Sustained) {
1330 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1332 update_note (ev_rect);
1336 MidiGhostRegion* gr;
1338 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1339 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1340 gr->add_note(ev_rect);
1344 } else if (midi_view()->note_mode() == Percussive) {
1346 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1348 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1350 update_hit (ev_diamond);
1359 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1360 note_selected(event, true);
1363 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1364 event->show_velocity();
1366 event->on_channel_selection_change(_last_channel_selection);
1367 _events.push_back(event);
1378 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1379 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1381 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1383 start_delta_command (_("step add"));
1384 delta_add_note (new_note, true, false);
1387 /* potentially extend region to hold new note */
1389 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1390 nframes64_t region_end = _region->position() + _region->length() - 1;
1392 if (end_frame > region_end) {
1393 _region->set_length (end_frame, this);
1400 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1402 assert(program.time >= 0);
1404 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1405 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1407 double height = midi_stream_view()->contents_height();
1409 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1410 new CanvasProgramChange(*this, *group,
1415 _custom_device_mode,
1416 program.time, program.channel, program.value));
1418 // Show unless program change is beyond the region bounds
1419 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1425 _pgm_changes.push_back(pgm_change);
1429 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1431 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1432 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1433 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1435 if (msb_control != 0) {
1436 msb = int(msb_control->get_float(true, time));
1437 cerr << "got msb " << msb;
1440 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1441 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1443 if (lsb_control != 0) {
1444 lsb = lsb_control->get_float(true, time);
1445 cerr << " got lsb " << lsb;
1448 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1449 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1450 float program_number = -1.0;
1451 if (program_control != 0) {
1452 program_number = program_control->get_float(true, time);
1453 cerr << " got program " << program_number << endl;
1456 key.msb = (int) floor(msb + 0.5);
1457 key.lsb = (int) floor(lsb + 0.5);
1458 key.program_number = (int) floor(program_number + 0.5);
1459 assert(key.is_sane());
1464 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1466 // TODO: Get the real event here and alter them at the original times
1467 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1468 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1469 if (msb_control != 0) {
1470 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1473 // TODO: Get the real event here and alter them at the original times
1474 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1475 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1476 if (lsb_control != 0) {
1477 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1480 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1481 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1483 assert(program_control != 0);
1484 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1490 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1492 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1493 alter_program_change(program_change_event, new_patch);
1497 MidiRegionView::previous_program(CanvasProgramChange& program)
1499 MIDI::Name::PatchPrimaryKey key;
1500 get_patch_key_at(program.event_time(), program.channel(), key);
1502 boost::shared_ptr<MIDI::Name::Patch> patch =
1503 MIDI::Name::MidiPatchManager::instance().previous_patch(
1505 _custom_device_mode,
1509 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1511 alter_program_change(program_change_event, patch->patch_primary_key());
1516 MidiRegionView::next_program(CanvasProgramChange& program)
1518 MIDI::Name::PatchPrimaryKey key;
1519 get_patch_key_at(program.event_time(), program.channel(), key);
1521 boost::shared_ptr<MIDI::Name::Patch> patch =
1522 MIDI::Name::MidiPatchManager::instance().next_patch(
1524 _custom_device_mode,
1528 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1530 alter_program_change(program_change_event, patch->patch_primary_key());
1535 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1537 if (_selection.empty()) {
1541 if (_selection.erase (cne) > 0) {
1542 cerr << "Erased a CNE from selection\n";
1547 MidiRegionView::delete_selection()
1549 if (_selection.empty()) {
1553 start_delta_command (_("delete selection"));
1555 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1556 if ((*i)->selected()) {
1557 _delta_command->remove((*i)->note());
1567 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1569 start_delta_command (_("delete note"));
1570 _delta_command->remove (n);
1573 trackview.editor().hide_verbose_canvas_cursor ();
1577 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1579 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1580 if ((*i)->selected() && (*i) != ev) {
1581 (*i)->selected(false);
1582 (*i)->hide_velocity();
1590 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1592 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1595 Selection::iterator tmp = i;
1598 (*i)->selected (false);
1599 _selection.erase (i);
1608 /* don't bother with removing this regionview from the editor selection,
1609 since we're about to add another note, and thus put/keep this
1610 regionview in the editor selection.
1613 if (!ev->selected()) {
1614 add_to_selection (ev);
1619 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1621 uint8_t low_note = 127;
1622 uint8_t high_note = 0;
1623 MidiModel::Notes& notes (_model->notes());
1624 _optimization_iterator = _events.begin();
1626 if (extend && _selection.empty()) {
1632 /* scan existing selection to get note range */
1634 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1635 if ((*i)->note()->note() < low_note) {
1636 low_note = (*i)->note()->note();
1638 if ((*i)->note()->note() > high_note) {
1639 high_note = (*i)->note()->note();
1643 low_note = min (low_note, notenum);
1644 high_note = max (high_note, notenum);
1647 no_sound_notes = true;
1649 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1651 boost::shared_ptr<NoteType> note (*n);
1652 CanvasNoteEvent* cne;
1653 bool select = false;
1655 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1657 if ((note->note() >= low_note && note->note() <= high_note)) {
1660 } else if (note->note() == notenum) {
1666 if ((cne = find_canvas_note (note)) != 0) {
1667 // extend is false because we've taken care of it,
1668 // since it extends by time range, not pitch.
1669 note_selected (cne, add, false);
1673 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1677 no_sound_notes = false;
1681 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1683 MidiModel::Notes& notes (_model->notes());
1684 _optimization_iterator = _events.begin();
1686 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1688 boost::shared_ptr<NoteType> note (*n);
1689 CanvasNoteEvent* cne;
1691 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1692 if ((cne = find_canvas_note (note)) != 0) {
1693 if (cne->selected()) {
1694 note_deselected (cne);
1696 note_selected (cne, true, false);
1704 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1707 clear_selection_except(ev);
1712 if (!ev->selected()) {
1713 add_to_selection (ev);
1717 /* find end of latest note selected, select all between that and the start of "ev" */
1719 Evoral::MusicalTime earliest = DBL_MAX;
1720 Evoral::MusicalTime latest = 0;
1722 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1723 if ((*i)->note()->end_time() > latest) {
1724 latest = (*i)->note()->end_time();
1726 if ((*i)->note()->time() < earliest) {
1727 earliest = (*i)->note()->time();
1731 if (ev->note()->end_time() > latest) {
1732 latest = ev->note()->end_time();
1735 if (ev->note()->time() < earliest) {
1736 earliest = ev->note()->time();
1739 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1741 /* find notes entirely within OR spanning the earliest..latest range */
1743 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1744 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1745 add_to_selection (*i);
1749 /* if events were guaranteed to be time sorted, we could do this.
1750 but as of sept 10th 2009, they no longer are.
1753 if ((*i)->note()->time() > latest) {
1762 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1764 remove_from_selection (ev);
1768 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1778 // TODO: Make this faster by storing the last updated selection rect, and only
1779 // adjusting things that are in the area that appears/disappeared.
1780 // We probably need a tree to be able to find events in O(log(n)) time.
1782 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1784 /* check if any corner of the note is inside the rect
1787 1) this is computing "touched by", not "contained by" the rect.
1788 2) this does not require that events be sorted in time.
1791 const double ix1 = (*i)->x1();
1792 const double ix2 = (*i)->x2();
1793 const double iy1 = (*i)->y1();
1794 const double iy2 = (*i)->y2();
1796 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1797 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1798 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1799 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1802 if (!(*i)->selected()) {
1803 add_to_selection (*i);
1805 } else if ((*i)->selected()) {
1806 // Not inside rectangle
1807 remove_from_selection (*i);
1813 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1815 Selection::iterator i = _selection.find (ev);
1817 if (i != _selection.end()) {
1818 _selection.erase (i);
1821 ev->selected (false);
1822 ev->hide_velocity ();
1824 if (_selection.empty()) {
1825 PublicEditor& editor (trackview.editor());
1826 editor.get_selection().remove (this);
1831 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1833 bool add_mrv_selection = false;
1835 if (_selection.empty()) {
1836 add_mrv_selection = true;
1839 if (_selection.insert (ev).second) {
1840 ev->selected (true);
1841 play_midi_note ((ev)->note());
1844 if (add_mrv_selection) {
1845 PublicEditor& editor (trackview.editor());
1846 editor.get_selection().add (this);
1851 MidiRegionView::move_selection(double dx, double dy)
1853 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1854 (*i)->move_event(dx, dy);
1859 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1861 assert (!_selection.empty());
1863 uint8_t lowest_note_in_selection = 127;
1864 uint8_t highest_note_in_selection = 0;
1865 uint8_t highest_note_difference = 0;
1867 // find highest and lowest notes first
1869 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1870 uint8_t pitch = (*i)->note()->note();
1871 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1872 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1876 cerr << "dnote: " << (int) dnote << endl;
1877 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1878 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1879 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1880 << int(highest_note_in_selection) << endl;
1881 cerr << "selection size: " << _selection.size() << endl;
1882 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1885 // Make sure the note pitch does not exceed the MIDI standard range
1886 if (highest_note_in_selection + dnote > 127) {
1887 highest_note_difference = highest_note_in_selection - 127;
1890 start_diff_command(_("move notes"));
1892 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1894 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1897 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1899 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1902 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1908 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1910 uint8_t original_pitch = (*i)->note()->note();
1911 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1913 // keep notes in standard midi range
1914 clamp_to_0_127(new_pitch);
1916 // keep original pitch if note is dragged outside valid midi range
1917 if ((original_pitch != 0 && new_pitch == 0)
1918 || (original_pitch != 127 && new_pitch == 127)) {
1919 new_pitch = original_pitch;
1922 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1923 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1925 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1930 // care about notes being moved beyond the upper/lower bounds on the canvas
1931 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1932 highest_note_in_selection > midi_stream_view()->highest_note()) {
1933 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1938 MidiRegionView::snap_pixel_to_frame(double x)
1940 PublicEditor& editor = trackview.editor();
1941 // x is region relative, convert it to global absolute frames
1942 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1943 editor.snap_to(frame);
1944 return frame - _region->position(); // convert back to region relative
1948 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1950 PublicEditor& editor = trackview.editor();
1951 // x is region relative, convert it to global absolute frames
1952 nframes64_t frame = x + _region->position();
1953 editor.snap_to(frame);
1954 return frame - _region->position(); // convert back to region relative
1958 MidiRegionView::snap_to_pixel(double x)
1960 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1964 MidiRegionView::get_position_pixels()
1966 nframes64_t region_frame = get_position();
1967 return trackview.editor().frame_to_pixel(region_frame);
1971 MidiRegionView::get_end_position_pixels()
1973 nframes64_t frame = get_position() + get_duration ();
1974 return trackview.editor().frame_to_pixel(frame);
1978 MidiRegionView::beats_to_frames(double beats) const
1980 return _time_converter.to(beats);
1984 MidiRegionView::frames_to_beats(nframes64_t frames) const
1986 return _time_converter.from(frames);
1990 MidiRegionView::begin_resizing (bool /*at_front*/)
1992 _resize_data.clear();
1994 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1995 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1997 // only insert CanvasNotes into the map
1999 NoteResizeData *resize_data = new NoteResizeData();
2000 resize_data->canvas_note = note;
2002 // create a new SimpleRect from the note which will be the resize preview
2003 SimpleRect *resize_rect = new SimpleRect(
2004 *group, note->x1(), note->y1(), note->x2(), note->y2());
2006 // calculate the colors: get the color settings
2007 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2008 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2011 // make the resize preview notes more transparent and bright
2012 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2014 // calculate color based on note velocity
2015 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2016 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
2020 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2021 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2023 resize_data->resize_rect = resize_rect;
2024 _resize_data.push_back(resize_data);
2030 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
2032 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2033 SimpleRect* resize_rect = (*i)->resize_rect;
2034 CanvasNote* canvas_note = (*i)->canvas_note;
2039 current_x = canvas_note->x1() + delta_x;
2041 // x is in track relative, transform it to region relative
2042 current_x = delta_x - get_position_pixels();
2046 current_x = canvas_note->x2() + delta_x;
2048 // x is in track relative, transform it to region relative
2049 current_x = delta_x - get_end_position_pixels ();
2054 resize_rect->property_x1() = snap_to_pixel(current_x);
2055 resize_rect->property_x2() = canvas_note->x2();
2057 resize_rect->property_x2() = snap_to_pixel(current_x);
2058 resize_rect->property_x1() = canvas_note->x1();
2064 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
2066 start_diff_command(_("resize notes"));
2068 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2069 CanvasNote* canvas_note = (*i)->canvas_note;
2070 SimpleRect* resize_rect = (*i)->resize_rect;
2071 const double region_start = get_position_pixels();
2076 current_x = canvas_note->x1() + delta_x;
2078 // x is in track relative, transform it to region relative
2079 current_x = region_start + delta_x;
2083 current_x = canvas_note->x2() + delta_x;
2085 // x is in track relative, transform it to region relative
2086 current_x = region_start + delta_x;
2090 current_x = snap_pixel_to_frame (current_x);
2091 current_x = frames_to_beats (current_x);
2093 if (at_front && current_x < canvas_note->note()->end_time()) {
2094 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2096 double len = canvas_note->note()->time() - current_x;
2097 len += canvas_note->note()->length();
2100 /* XXX convert to beats */
2101 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2106 double len = current_x - canvas_note->note()->time();
2109 /* XXX convert to beats */
2110 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2118 _resize_data.clear();
2123 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2125 uint8_t new_velocity;
2128 new_velocity = event->note()->velocity() + velocity;
2129 clamp_to_0_127(new_velocity);
2131 new_velocity = velocity;
2134 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2138 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2143 new_note = event->note()->note() + note;
2148 clamp_to_0_127 (new_note);
2149 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2153 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2155 bool change_start = false;
2156 bool change_length = false;
2157 Evoral::MusicalTime new_start;
2158 Evoral::MusicalTime new_length;
2160 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2162 front_delta: if positive - move the start of the note later in time (shortening it)
2163 if negative - move the start of the note earlier in time (lengthening it)
2165 end_delta: if positive - move the end of the note later in time (lengthening it)
2166 if negative - move the end of the note earlier in time (shortening it)
2170 if (front_delta < 0) {
2172 if (event->note()->time() < -front_delta) {
2175 new_start = event->note()->time() + front_delta; // moves earlier
2178 /* start moved toward zero, so move the end point out to where it used to be.
2179 Note that front_delta is negative, so this increases the length.
2182 new_length = event->note()->length() - front_delta;
2183 change_start = true;
2184 change_length = true;
2188 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2190 if (new_pos < event->note()->end_time()) {
2191 new_start = event->note()->time() + front_delta;
2192 /* start moved toward the end, so move the end point back to where it used to be */
2193 new_length = event->note()->length() - front_delta;
2194 change_start = true;
2195 change_length = true;
2202 bool can_change = true;
2203 if (end_delta < 0) {
2204 if (event->note()->length() < -end_delta) {
2210 new_length = event->note()->length() + end_delta;
2211 change_length = true;
2216 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2219 if (change_length) {
2220 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2225 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2227 Evoral::MusicalTime new_time;
2231 if (event->note()->time() < -delta) {
2234 new_time = event->note()->time() + delta;
2237 new_time = event->note()->time() + delta;
2243 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2247 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2251 if (_selection.empty()) {
2266 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2267 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2273 start_diff_command(_("change velocities"));
2275 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2276 Selection::iterator next = i;
2278 change_note_velocity (*i, delta, true);
2287 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2289 if (_selection.empty()) {
2306 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2308 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2312 if ((int8_t) (*i)->note()->note() + delta > 127) {
2319 start_diff_command (_("transpose"));
2321 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2322 Selection::iterator next = i;
2324 change_note_note (*i, delta, true);
2332 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2334 Evoral::MusicalTime delta;
2339 /* grab the current grid distance */
2341 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2343 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2344 cerr << "Grid type not available as beats - TO BE FIXED\n";
2353 start_diff_command (_("change note lengths"));
2355 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2356 Selection::iterator next = i;
2359 /* note the negation of the delta for start */
2361 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2370 MidiRegionView::nudge_notes (bool forward)
2372 if (_selection.empty()) {
2376 /* pick a note as the point along the timeline to get the nudge distance.
2377 its not necessarily the earliest note, so we may want to pull the notes out
2378 into a vector and sort before using the first one.
2381 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2383 nframes64_t distance;
2385 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2387 /* grid is off - use nudge distance */
2389 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2395 nframes64_t next_pos = ref_point;
2398 /* XXX need check on max_frames, but that needs max_frames64 or something */
2401 if (next_pos == 0) {
2407 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2408 distance = ref_point - next_pos;
2411 if (distance == 0) {
2415 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2421 start_diff_command (_("nudge"));
2423 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2424 Selection::iterator next = i;
2426 change_note_time (*i, delta, true);
2434 MidiRegionView::change_channel(uint8_t channel)
2436 start_diff_command(_("change channel"));
2437 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2438 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2445 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2447 if (_mouse_state == SelectTouchDragging) {
2448 note_selected(ev, true);
2451 show_verbose_canvas_cursor (ev->note ());
2455 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2457 PublicEditor& editor (trackview.editor());
2458 editor.hide_verbose_canvas_cursor ();
2463 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2465 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2467 display_model(msrc->model());
2471 MidiRegionView::set_frame_color()
2474 if (_selected && should_show_selection) {
2475 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2477 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2483 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2487 case FilterChannels:
2488 _force_channel = -1;
2491 _force_channel = mask;
2492 mask = 0xFFFF; // Show all notes as active (below)
2495 // Update notes for selection
2496 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2497 (*i)->on_channel_selection_change(mask);
2500 _last_channel_selection = mask;
2504 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2506 _model_name = model;
2507 _custom_device_mode = custom_device_mode;
2512 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2514 if (_selection.empty()) {
2518 PublicEditor& editor (trackview.editor());
2523 editor.get_cut_buffer().add (selection_as_cut_buffer());
2531 start_delta_command();
2533 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2539 delta_remove_note (*i);
2549 MidiRegionView::selection_as_cut_buffer () const
2553 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2554 NoteType* n = (*i)->note().get();
2555 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2558 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2565 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2571 start_delta_command (_("paste"));
2573 Evoral::MusicalTime beat_delta;
2574 Evoral::MusicalTime paste_pos_beats;
2575 Evoral::MusicalTime duration;
2576 Evoral::MusicalTime end_point;
2578 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2579 paste_pos_beats = frames_to_beats (pos - _region->position());
2580 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2581 paste_pos_beats = 0;
2583 _selection.clear ();
2585 for (int n = 0; n < (int) times; ++n) {
2587 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2589 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2591 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2592 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2594 /* make all newly added notes selected */
2596 delta_add_note (copied_note, true);
2597 end_point = copied_note->end_time();
2600 paste_pos_beats += duration;
2603 /* if we pasted past the current end of the region, extend the region */
2605 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2606 nframes64_t region_end = _region->position() + _region->length() - 1;
2608 if (end_frame > region_end) {
2610 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2612 trackview.session()->begin_reversible_command (_("paste"));
2614 _region->clear_history ();
2615 _region->set_length (end_frame, this);
2616 trackview.session()->add_command (new StatefulDiffCommand (_region));
2619 cerr << "region end finally at " << _region->position() + _region->length() - 1;
2623 struct EventNoteTimeEarlyFirstComparator {
2624 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2625 return a->note()->time() < b->note()->time();
2630 MidiRegionView::time_sort_events ()
2632 if (!_sort_needed) {
2636 EventNoteTimeEarlyFirstComparator cmp;
2639 _sort_needed = false;
2643 MidiRegionView::goto_next_note ()
2645 // nframes64_t pos = -1;
2646 bool use_next = false;
2648 if (_events.back()->selected()) {
2652 time_sort_events ();
2654 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2655 if ((*i)->selected()) {
2658 } else if (use_next) {
2660 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2665 /* use the first one */
2667 unique_select (_events.front());
2672 MidiRegionView::goto_previous_note ()
2674 // nframes64_t pos = -1;
2675 bool use_next = false;
2677 if (_events.front()->selected()) {
2681 time_sort_events ();
2683 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2684 if ((*i)->selected()) {
2687 } else if (use_next) {
2689 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2694 /* use the last one */
2696 unique_select (*(_events.rbegin()));
2700 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2702 bool had_selected = false;
2704 time_sort_events ();
2706 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2707 if ((*i)->selected()) {
2708 selected.insert ((*i)->note());
2709 had_selected = true;
2713 if (allow_all_if_none_selected && !had_selected) {
2714 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2715 selected.insert ((*i)->note());
2721 MidiRegionView::update_ghost_note (double x, double y)
2727 nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2728 trackview.editor().snap_to (f);
2729 f -= _region->position ();
2732 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2737 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2739 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2740 _ghost_note->note()->set_length (length);
2741 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2743 update_note (_ghost_note);
2745 show_verbose_canvas_cursor (_ghost_note->note ());
2749 MidiRegionView::create_ghost_note (double x, double y)
2754 boost::shared_ptr<NoteType> g (new NoteType);
2755 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2756 update_ghost_note (x, y);
2757 _ghost_note->show ();
2762 show_verbose_canvas_cursor (_ghost_note->note ());
2766 MidiRegionView::snap_changed ()
2772 create_ghost_note (_last_ghost_x, _last_ghost_y);
2776 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2779 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (n->note()).c_str(), (int) n->note ());
2780 trackview.editor().show_verbose_canvas_cursor_with (buf);