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"
33 #include "ardour/playlist.h"
34 #include "ardour/tempo.h"
35 #include "ardour/midi_region.h"
36 #include "ardour/midi_source.h"
37 #include "ardour/midi_diskstream.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"
45 #include "automation_region_view.h"
46 #include "automation_time_axis.h"
47 #include "canvas-hit.h"
48 #include "canvas-note.h"
49 #include "canvas-program-change.h"
50 #include "ghostregion.h"
51 #include "gui_thread.h"
53 #include "midi_cut_buffer.h"
54 #include "midi_list_editor.h"
55 #include "midi_region_view.h"
56 #include "midi_streamview.h"
57 #include "midi_time_axis.h"
58 #include "midi_time_axis.h"
59 #include "midi_util.h"
60 #include "public_editor.h"
61 #include "selection.h"
62 #include "simpleline.h"
63 #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 , _default_note_length(1.0)
81 , _current_range_min(0)
82 , _current_range_max(0)
83 , _model_name(string())
84 , _custom_device_mode(string())
86 , _note_group(new ArdourCanvas::Group(*parent))
92 , _optimization_iterator (_events.end())
94 _note_group->raise_to_top();
97 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
98 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
99 TimeAxisViewItem::Visibility visibility)
100 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
102 , _last_channel_selection(0xFFFF)
103 , _default_note_length(1.0)
104 , _model_name(string())
105 , _custom_device_mode(string())
107 , _note_group(new ArdourCanvas::Group(*parent))
112 , _sort_needed (true)
113 , _optimization_iterator (_events.end())
116 _note_group->raise_to_top();
120 MidiRegionView::MidiRegionView (const MidiRegionView& other)
121 : sigc::trackable(other)
124 , _last_channel_selection(0xFFFF)
125 , _default_note_length(1.0)
126 , _model_name(string())
127 , _custom_device_mode(string())
129 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
134 , _sort_needed (true)
135 , _optimization_iterator (_events.end())
140 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
141 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
146 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
147 : RegionView (other, boost::shared_ptr<Region> (region))
149 , _last_channel_selection(0xFFFF)
150 , _default_note_length(1.0)
151 , _model_name(string())
152 , _custom_device_mode(string())
154 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
159 , _sort_needed (true)
160 , _optimization_iterator (_events.end())
165 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
166 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
172 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
175 midi_region()->midi_source(0)->load_model();
178 _model = midi_region()->midi_source(0)->model();
179 _enable_display = false;
181 RegionView::init (basic_color, false);
183 compute_colors (basic_color);
185 set_height (trackview.current_height());
188 region_sync_changed ();
189 region_resized (BoundsChanged);
192 reset_width_dependent_items (_pixel_width);
196 _enable_display = true;
199 display_model (_model);
203 group->raise_to_top();
204 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
206 midi_view()->signal_channel_mode_changed().connect(
207 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
209 midi_view()->signal_midi_patch_settings_changed().connect(
210 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
214 MidiRegionView::canvas_event(GdkEvent* ev)
216 PublicEditor& editor (trackview.editor());
218 if (!editor.internal_editing()) {
222 static double drag_start_x, drag_start_y;
223 static double last_x, last_y;
224 double event_x, event_y;
225 nframes64_t event_frame = 0;
228 static ArdourCanvas::SimpleRect* drag_rect = 0;
230 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
231 to its items, which means that ev->type == GDK_SCROLL will never be seen
236 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
238 if (ev->scroll.direction == GDK_SCROLL_UP) {
239 change_velocities (true, fine, false);
241 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
242 change_velocities (false, fine, false);
251 /* since GTK bindings are generally activated on press, and since
252 detectable auto-repeat is the name of the game and only sends
253 repeated presses, carry out key actions at key press, not release.
256 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
257 _mouse_state = SelectTouchDragging;
260 } else if (ev->key.keyval == GDK_Escape) {
264 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
266 bool start = (ev->key.keyval == GDK_comma);
267 bool end = (ev->key.keyval == GDK_period);
268 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
269 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
271 change_note_lengths (fine, shorter, start, end);
275 } else if (ev->key.keyval == GDK_Delete) {
280 } else if (ev->key.keyval == GDK_Tab) {
282 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
283 goto_previous_note ();
289 } else if (ev->key.keyval == GDK_Up) {
291 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
292 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
294 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
295 change_velocities (true, fine, allow_smush);
297 transpose (true, fine, allow_smush);
301 } else if (ev->key.keyval == GDK_Down) {
303 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
304 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
306 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
307 change_velocities (false, fine, allow_smush);
309 transpose (false, fine, allow_smush);
313 } else if (ev->key.keyval == GDK_Left) {
318 } else if (ev->key.keyval == GDK_Right) {
323 } else if (ev->key.keyval == GDK_Control_L) {
326 } else if (ev->key.keyval == GDK_r) {
327 /* if we're not step editing, this really doesn't matter */
328 midi_view()->step_edit_rest ();
334 case GDK_KEY_RELEASE:
335 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
341 case GDK_BUTTON_PRESS:
342 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
343 _pressed_button = ev->button.button;
344 _mouse_state = Pressed;
347 _pressed_button = ev->button.button;
350 case GDK_2BUTTON_PRESS:
353 case GDK_ENTER_NOTIFY:
354 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
355 Keyboard::magic_widget_grab_focus();
359 case GDK_MOTION_NOTIFY:
360 event_x = ev->motion.x;
361 event_y = ev->motion.y;
362 group->w2i(event_x, event_y);
364 // convert event_x to global frame
365 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
366 trackview.editor().snap_to(event_frame);
367 // convert event_frame back to local coordinates relative to position
368 event_frame -= _region->position();
370 switch (_mouse_state) {
371 case Pressed: // Drag start
374 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
375 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
376 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
379 drag_start_x = event_x;
380 drag_start_y = event_y;
382 drag_rect = new ArdourCanvas::SimpleRect(*group);
383 drag_rect->property_x1() = event_x;
384 drag_rect->property_y1() = event_y;
385 drag_rect->property_x2() = event_x;
386 drag_rect->property_y2() = event_y;
387 drag_rect->property_outline_what() = 0xFF;
388 drag_rect->property_outline_color_rgba()
389 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
390 drag_rect->property_fill_color_rgba()
391 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
393 _mouse_state = SelectRectDragging;
396 // Add note drag start
397 } else if (editor.current_mouse_mode() == MouseRange) {
398 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
399 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
402 drag_start_x = event_x;
403 drag_start_y = event_y;
405 drag_rect = new ArdourCanvas::SimpleRect(*group);
406 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
408 drag_rect->property_y1() = midi_stream_view()->note_to_y(
409 midi_stream_view()->y_to_note(event_y));
410 drag_rect->property_x2() = event_x;
411 drag_rect->property_y2() = drag_rect->property_y1()
412 + floor(midi_stream_view()->note_height());
413 drag_rect->property_outline_what() = 0xFF;
414 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
415 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
417 _mouse_state = AddDragging;
423 case SelectRectDragging: // Select drag motion
424 case AddDragging: // Add note drag motion
425 if (ev->motion.is_hint) {
428 GdkModifierType state;
429 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
434 if (_mouse_state == AddDragging)
435 event_x = trackview.editor().frame_to_pixel(event_frame);
438 if (event_x > drag_start_x)
439 drag_rect->property_x2() = event_x;
441 drag_rect->property_x1() = event_x;
444 if (drag_rect && _mouse_state == SelectRectDragging) {
445 if (event_y > drag_start_y)
446 drag_rect->property_y2() = event_y;
448 drag_rect->property_y1() = event_y;
450 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
456 case SelectTouchDragging:
464 case GDK_BUTTON_RELEASE:
465 event_x = ev->motion.x;
466 event_y = ev->motion.y;
467 group->w2i(event_x, event_y);
468 group->ungrab(ev->button.time);
469 event_frame = trackview.editor().pixel_to_frame(event_x);
471 if (ev->button.button == 3) {
473 } else if (_pressed_button != 1) {
477 switch (_mouse_state) {
478 case Pressed: // Clicked
479 switch (editor.current_mouse_mode()) {
485 create_note_at(event_x, event_y, _default_note_length);
492 case SelectRectDragging: // Select drag done
497 case AddDragging: // Add drag done
499 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
500 const double x = drag_rect->property_x1();
501 const double length = trackview.editor().pixel_to_frame(
502 drag_rect->property_x2() - drag_rect->property_x1());
504 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
519 MidiRegionView::show_list_editor ()
521 MidiListEditor* mle = new MidiListEditor (trackview.session(), midi_region());
525 /** Add a note to the model, and the view, at a canvas (click) coordinate.
526 * \param x horizontal position in pixels
527 * \param y vertical position in pixels
528 * \param length duration of the note in beats */
530 MidiRegionView::create_note_at(double x, double y, double length)
532 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
533 MidiStreamView* const view = mtv->midi_view();
535 double note = midi_stream_view()->y_to_note(y);
538 assert(note <= 127.0);
540 // Start of note in frames relative to region start
541 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
542 assert(start_frames >= 0);
545 length = frames_to_beats(
546 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
548 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
549 frames_to_beats(start_frames + _region->start()), length,
550 (uint8_t)note, 0x40));
552 view->update_note_range(new_note->note());
554 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
556 _model->apply_command(trackview.session(), cmd);
558 play_midi_note (new_note);
562 MidiRegionView::clear_events()
567 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
568 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
573 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
578 _pgm_changes.clear();
580 _optimization_iterator = _events.end();
585 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
588 content_connection.disconnect ();
589 content_connection = _model->ContentsChanged.connect(
590 sigc::mem_fun(this, &MidiRegionView::redisplay_model));
593 if (_enable_display) {
600 MidiRegionView::start_delta_command(string name)
602 if (!_delta_command) {
603 _delta_command = _model->new_delta_command(name);
608 MidiRegionView::start_diff_command(string name)
610 if (!_diff_command) {
611 _diff_command = _model->new_diff_command(name);
616 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
618 if (_delta_command) {
619 _delta_command->add(note);
622 _marked_for_selection.insert(note);
625 _marked_for_velocity.insert(note);
630 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
632 if (_delta_command && ev->note()) {
633 _delta_command->remove(ev->note());
638 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
639 MidiModel::DiffCommand::Property property,
643 _diff_command->change (ev->note(), property, val);
648 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
649 MidiModel::DiffCommand::Property property,
650 Evoral::MusicalTime val)
653 _diff_command->change (ev->note(), property, val);
658 MidiRegionView::apply_delta()
660 if (!_delta_command) {
664 // Mark all selected notes for selection when model reloads
665 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
666 _marked_for_selection.insert((*i)->note());
669 _model->apply_command(trackview.session(), _delta_command);
671 midi_view()->midi_track()->diskstream()->playlist_modified();
673 _marked_for_selection.clear();
674 _marked_for_velocity.clear();
678 MidiRegionView::apply_diff ()
680 if (!_diff_command) {
684 _model->apply_command(trackview.session(), _diff_command);
686 midi_view()->midi_track()->diskstream()->playlist_modified();
688 _marked_for_velocity.clear();
692 MidiRegionView::apply_delta_as_subcommand()
694 if (!_delta_command) {
698 // Mark all selected notes for selection when model reloads
699 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
700 _marked_for_selection.insert((*i)->note());
703 _model->apply_command_as_subcommand(trackview.session(), _delta_command);
705 midi_view()->midi_track()->diskstream()->playlist_modified();
707 _marked_for_selection.clear();
708 _marked_for_velocity.clear();
712 MidiRegionView::apply_diff_as_subcommand()
714 if (!_diff_command) {
718 // Mark all selected notes for selection when model reloads
719 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
720 _marked_for_selection.insert((*i)->note());
723 _model->apply_command_as_subcommand(trackview.session(), _diff_command);
725 midi_view()->midi_track()->diskstream()->playlist_modified();
727 _marked_for_selection.clear();
728 _marked_for_velocity.clear();
732 MidiRegionView::abort_command()
734 delete _delta_command;
736 delete _diff_command;
742 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
744 if (_optimization_iterator != _events.end()) {
745 ++_optimization_iterator;
748 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
749 return *_optimization_iterator;
752 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
753 if ((*_optimization_iterator)->note() == note) {
754 return *_optimization_iterator;
762 MidiRegionView::redisplay_model()
764 // Don't redisplay the model if we're currently recording and displaying that
770 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
774 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
778 MidiModel::ReadLock lock(_model->read_lock());
780 MidiModel::Notes& notes (_model->notes());
781 _optimization_iterator = _events.begin();
783 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
785 boost::shared_ptr<NoteType> note (*n);
786 CanvasNoteEvent* cne;
789 if (note_in_region_range (note, visible)) {
791 if ((cne = find_canvas_note (note)) != 0) {
798 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
800 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
812 add_note (note, visible);
817 if ((cne = find_canvas_note (note)) != 0) {
824 /* remove note items that are no longer valid */
826 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
827 if (!(*i)->valid ()) {
829 i = _events.erase (i);
836 display_program_changes();
838 _marked_for_selection.clear ();
839 _marked_for_velocity.clear ();
841 /* we may have caused _events to contain things out of order (e.g. if a note
842 moved earlier or later). we don't generally need them in time order, but
843 make a note that a sort is required for those cases that require it.
850 MidiRegionView::display_program_changes()
852 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
857 Glib::Mutex::Lock lock (control->list()->lock());
859 uint8_t channel = control->parameter().channel();
861 for (AutomationList::const_iterator event = control->list()->begin();
862 event != control->list()->end(); ++event) {
863 double event_time = (*event)->when;
864 double program_number = floor((*event)->value + 0.5);
866 // Get current value of bank select MSB at time of the program change
867 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
868 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
870 if (msb_control != 0) {
871 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
874 // Get current value of bank select LSB at time of the program change
875 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
876 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
878 if (lsb_control != 0) {
879 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
882 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
884 boost::shared_ptr<MIDI::Name::Patch> patch =
885 MIDI::Name::MidiPatchManager::instance().find_patch(
886 _model_name, _custom_device_mode, channel, patch_key);
888 PCEvent program_change(event_time, uint8_t(program_number), channel);
891 add_pgm_change(program_change, patch->name());
894 snprintf(buf, 4, "%d", int(program_number));
895 add_pgm_change(program_change, buf);
901 MidiRegionView::display_sysexes()
903 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
904 Evoral::MusicalTime time = (*i)->time();
909 for (uint32_t b = 0; b < (*i)->size(); ++b) {
910 str << int((*i)->buffer()[b]);
911 if (b != (*i)->size() -1) {
915 string text = str.str();
917 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
919 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
921 double height = midi_stream_view()->contents_height();
923 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
924 new CanvasSysEx(*this, *group, text, height, x, 1.0));
926 // Show unless program change is beyond the region bounds
927 if (time - _region->start() >= _region->length() || time < _region->start()) {
933 _sys_exes.push_back(sysex);
938 MidiRegionView::~MidiRegionView ()
940 in_destructor = true;
942 RegionViewGoingAway (this); /* EMIT_SIGNAL */
951 delete _delta_command;
955 MidiRegionView::region_resized (Change what_changed)
957 RegionView::region_resized(what_changed);
959 if (what_changed & ARDOUR::PositionChanged) {
960 set_duration(_region->length(), 0);
961 if (_enable_display) {
968 MidiRegionView::reset_width_dependent_items (double pixel_width)
970 RegionView::reset_width_dependent_items(pixel_width);
971 assert(_pixel_width == pixel_width);
973 if (_enable_display) {
979 MidiRegionView::set_height (double height)
981 static const double FUDGE = 2.0;
982 const double old_height = _height;
983 RegionView::set_height(height);
984 _height = height - FUDGE;
986 apply_note_range(midi_stream_view()->lowest_note(),
987 midi_stream_view()->highest_note(),
988 height != old_height + FUDGE);
991 name_pixbuf->raise_to_top();
996 /** Apply the current note range from the stream view
997 * by repositioning/hiding notes as necessary
1000 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1002 if (!_enable_display) {
1006 if (!force && _current_range_min == min && _current_range_max == max) {
1010 _current_range_min = min;
1011 _current_range_max = max;
1013 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1014 CanvasNoteEvent* event = *i;
1015 boost::shared_ptr<NoteType> note (event->note());
1017 if (note->note() < _current_range_min ||
1018 note->note() > _current_range_max) {
1024 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1026 const double y1 = midi_stream_view()->note_to_y(note->note());
1027 const double y2 = y1 + floor(midi_stream_view()->note_height());
1029 cnote->property_y1() = y1;
1030 cnote->property_y2() = y2;
1032 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1034 double x = trackview.editor().frame_to_pixel(
1035 beats_to_frames(note->time()) - _region->start());
1036 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1037 double y = midi_stream_view()->note_to_y(event->note()->note())
1038 + ((diamond_size-2.0) / 4.0);
1040 chit->set_height (diamond_size);
1041 chit->move (x - chit->x1(), y - chit->y1());
1048 MidiRegionView::add_ghost (TimeAxisView& tv)
1052 double unit_position = _region->position () / samples_per_unit;
1053 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1054 MidiGhostRegion* ghost;
1056 if (mtv && mtv->midi_view()) {
1057 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1058 to allow having midi notes on top of note lines and waveforms.
1060 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1062 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1065 ghost->set_height ();
1066 ghost->set_duration (_region->length() / samples_per_unit);
1067 ghosts.push_back (ghost);
1069 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1070 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1071 ghost->add_note(note);
1075 ghost->GoingAway.connect (sigc::mem_fun(*this, &MidiRegionView::remove_ghost));
1081 /** Begin tracking note state for successive calls to add_event
1084 MidiRegionView::begin_write()
1086 assert(!_active_notes);
1087 _active_notes = new CanvasNote*[128];
1088 for (unsigned i=0; i < 128; ++i) {
1089 _active_notes[i] = 0;
1094 /** Destroy note state for add_event
1097 MidiRegionView::end_write()
1099 delete[] _active_notes;
1101 _marked_for_selection.clear();
1102 _marked_for_velocity.clear();
1106 /** Resolve an active MIDI note (while recording).
1109 MidiRegionView::resolve_note(uint8_t note, double end_time)
1111 if (midi_view()->note_mode() != Sustained) {
1115 if (_active_notes && _active_notes[note]) {
1116 const nframes64_t end_time_frames = beats_to_frames(end_time);
1117 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1118 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1119 _active_notes[note] = 0;
1124 /** Extend active notes to rightmost edge of region (if length is changed)
1127 MidiRegionView::extend_active_notes()
1129 if (!_active_notes) {
1133 for (unsigned i=0; i < 128; ++i) {
1134 if (_active_notes[i]) {
1135 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1141 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1143 if (!trackview.editor().sound_notes()) {
1147 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1150 route_ui->midi_track()->write_immediate_event(
1151 note->on_event().size(), note->on_event().buffer());
1153 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1154 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1155 * (1000 / (double)route_ui->session().nominal_frame_rate());
1156 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1157 note_length_ms, G_PRIORITY_DEFAULT);
1161 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1163 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1166 route_ui->midi_track()->write_immediate_event(
1167 note->off_event().size(), note->off_event().buffer());
1173 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1175 const nframes64_t note_start_frames = beats_to_frames(note->time());
1177 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1178 (note_start_frames < _region->start());
1180 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1181 (note->note() <= midi_stream_view()->highest_note());
1187 MidiRegionView::update_note (CanvasNote* ev)
1189 boost::shared_ptr<NoteType> note = ev->note();
1191 const nframes64_t note_start_frames = beats_to_frames(note->time());
1192 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1194 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1195 const double y1 = midi_stream_view()->note_to_y(note->note());
1196 const double note_endpixel =
1197 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1199 ev->property_x1() = x;
1200 ev->property_y1() = y1;
1201 if (note->length() > 0) {
1202 ev->property_x2() = note_endpixel;
1204 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1206 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1208 if (note->length() == 0) {
1209 if (_active_notes) {
1210 assert(note->note() < 128);
1211 // If this note is already active there's a stuck note,
1212 // finish the old note rectangle
1213 if (_active_notes[note->note()]) {
1214 CanvasNote* const old_rect = _active_notes[note->note()];
1215 boost::shared_ptr<NoteType> old_note = old_rect->note();
1216 old_rect->property_x2() = x;
1217 old_rect->property_outline_what() = (guint32) 0xF;
1219 _active_notes[note->note()] = ev;
1221 /* outline all but right edge */
1222 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1224 /* outline all edges */
1225 ev->property_outline_what() = (guint32) 0xF;
1230 MidiRegionView::update_hit (CanvasHit* ev)
1232 boost::shared_ptr<NoteType> note = ev->note();
1234 const nframes64_t note_start_frames = beats_to_frames(note->time());
1235 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1236 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1237 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1242 /** Add a MIDI note to the view (with length).
1244 * If in sustained mode, notes with length 0 will be considered active
1245 * notes, and resolve_note should be called when the corresponding note off
1246 * event arrives, to properly display the note.
1249 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1251 CanvasNoteEvent* event = 0;
1253 assert(note->time() >= 0);
1254 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1256 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1258 if (midi_view()->note_mode() == Sustained) {
1260 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1262 update_note (ev_rect);
1266 MidiGhostRegion* gr;
1268 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1269 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1270 gr->add_note(ev_rect);
1274 } else if (midi_view()->note_mode() == Percussive) {
1276 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1278 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1280 update_hit (ev_diamond);
1289 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1290 note_selected(event, true);
1293 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1294 event->show_velocity();
1296 event->on_channel_selection_change(_last_channel_selection);
1297 _events.push_back(event);
1308 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1309 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1311 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1313 start_delta_command (_("step add"));
1314 delta_add_note (new_note, true, false);
1317 /* potentially extend region to hold new note */
1319 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1320 nframes64_t region_end = _region->position() + _region->length() - 1;
1322 if (end_frame > region_end) {
1323 _region->set_length (end_frame, this);
1330 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1332 assert(program.time >= 0);
1334 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1335 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1337 double height = midi_stream_view()->contents_height();
1339 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1340 new CanvasProgramChange(*this, *group,
1345 _custom_device_mode,
1346 program.time, program.channel, program.value));
1348 // Show unless program change is beyond the region bounds
1349 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1355 _pgm_changes.push_back(pgm_change);
1359 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1361 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1362 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1363 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1365 if (msb_control != 0) {
1366 msb = int(msb_control->get_float(true, time));
1367 cerr << "got msb " << msb;
1370 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1371 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1373 if (lsb_control != 0) {
1374 lsb = lsb_control->get_float(true, time);
1375 cerr << " got lsb " << lsb;
1378 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1379 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1380 float program_number = -1.0;
1381 if (program_control != 0) {
1382 program_number = program_control->get_float(true, time);
1383 cerr << " got program " << program_number << endl;
1386 key.msb = (int) floor(msb + 0.5);
1387 key.lsb = (int) floor(lsb + 0.5);
1388 key.program_number = (int) floor(program_number + 0.5);
1389 assert(key.is_sane());
1394 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1396 // TODO: Get the real event here and alter them at the original times
1397 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1398 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1399 if (msb_control != 0) {
1400 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1403 // TODO: Get the real event here and alter them at the original times
1404 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1405 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1406 if (lsb_control != 0) {
1407 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1410 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1411 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1413 assert(program_control != 0);
1414 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1420 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1422 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1423 alter_program_change(program_change_event, new_patch);
1427 MidiRegionView::previous_program(CanvasProgramChange& program)
1429 MIDI::Name::PatchPrimaryKey key;
1430 get_patch_key_at(program.event_time(), program.channel(), key);
1432 boost::shared_ptr<MIDI::Name::Patch> patch =
1433 MIDI::Name::MidiPatchManager::instance().previous_patch(
1435 _custom_device_mode,
1439 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1441 alter_program_change(program_change_event, patch->patch_primary_key());
1446 MidiRegionView::next_program(CanvasProgramChange& program)
1448 MIDI::Name::PatchPrimaryKey key;
1449 get_patch_key_at(program.event_time(), program.channel(), key);
1451 boost::shared_ptr<MIDI::Name::Patch> patch =
1452 MIDI::Name::MidiPatchManager::instance().next_patch(
1454 _custom_device_mode,
1458 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1460 alter_program_change(program_change_event, patch->patch_primary_key());
1465 MidiRegionView::delete_selection()
1467 if (_selection.empty()) {
1471 start_delta_command (_("delete selection"));
1473 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1474 if ((*i)->selected()) {
1475 _delta_command->remove((*i)->note());
1485 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1487 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1488 if ((*i)->selected() && (*i) != ev) {
1489 (*i)->selected(false);
1490 (*i)->hide_velocity();
1498 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1500 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1503 Selection::iterator tmp = i;
1506 (*i)->selected (false);
1507 _selection.erase (i);
1516 /* don't bother with removing this regionview from the editor selection,
1517 since we're about to add another note, and thus put/keep this
1518 regionview in the editor selection.
1521 if (!ev->selected()) {
1522 add_to_selection (ev);
1527 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1530 clear_selection_except(ev);
1535 if (!ev->selected()) {
1536 add_to_selection (ev);
1540 /* find end of latest note selected, select all between that and the start of "ev" */
1542 Evoral::MusicalTime earliest = DBL_MAX;
1543 Evoral::MusicalTime latest = 0;
1545 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1546 if ((*i)->note()->end_time() > latest) {
1547 latest = (*i)->note()->end_time();
1549 if ((*i)->note()->time() < earliest) {
1550 earliest = (*i)->note()->time();
1554 if (ev->note()->end_time() > latest) {
1555 latest = ev->note()->end_time();
1558 if (ev->note()->time() < earliest) {
1559 earliest = ev->note()->time();
1562 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1564 /* find notes entirely within OR spanning the earliest..latest range */
1566 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1567 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1568 add_to_selection (*i);
1572 /* if events were guaranteed to be time sorted, we could do this.
1573 but as of sept 10th 2009, they no longer are.
1576 if ((*i)->note()->time() > latest) {
1585 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1587 remove_from_selection (ev);
1591 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1601 // TODO: Make this faster by storing the last updated selection rect, and only
1602 // adjusting things that are in the area that appears/disappeared.
1603 // We probably need a tree to be able to find events in O(log(n)) time.
1605 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1607 /* check if any corner of the note is inside the rect
1610 1) this is computing "touched by", not "contained by" the rect.
1611 2) this does not require that events be sorted in time.
1614 const double ix1 = (*i)->x1();
1615 const double ix2 = (*i)->x2();
1616 const double iy1 = (*i)->y1();
1617 const double iy2 = (*i)->y2();
1619 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1620 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1621 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1622 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1625 if (!(*i)->selected()) {
1626 add_to_selection (*i);
1628 } else if ((*i)->selected()) {
1629 // Not inside rectangle
1630 remove_from_selection (*i);
1636 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1638 Selection::iterator i = _selection.find (ev);
1640 if (i != _selection.end()) {
1641 _selection.erase (i);
1644 ev->selected (false);
1645 ev->hide_velocity ();
1647 if (_selection.empty()) {
1648 PublicEditor& editor (trackview.editor());
1649 editor.get_selection().remove (this);
1654 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1656 bool add_mrv_selection = false;
1658 if (_selection.empty()) {
1659 add_mrv_selection = true;
1662 if (_selection.insert (ev).second) {
1663 ev->selected (true);
1664 play_midi_note ((ev)->note());
1667 if (add_mrv_selection) {
1668 PublicEditor& editor (trackview.editor());
1669 editor.get_selection().add (this);
1674 MidiRegionView::move_selection(double dx, double dy)
1676 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1677 (*i)->move_event(dx, dy);
1682 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1684 assert (!_selection.empty());
1686 uint8_t lowest_note_in_selection = 127;
1687 uint8_t highest_note_in_selection = 0;
1688 uint8_t highest_note_difference = 0;
1690 // find highest and lowest notes first
1692 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1693 uint8_t pitch = (*i)->note()->note();
1694 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1695 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1699 cerr << "dnote: " << (int) dnote << endl;
1700 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1701 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1702 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1703 << int(highest_note_in_selection) << endl;
1704 cerr << "selection size: " << _selection.size() << endl;
1705 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1708 // Make sure the note pitch does not exceed the MIDI standard range
1709 if (highest_note_in_selection + dnote > 127) {
1710 highest_note_difference = highest_note_in_selection - 127;
1713 start_diff_command(_("move notes"));
1715 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1717 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1720 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1722 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1725 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1731 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1733 uint8_t original_pitch = (*i)->note()->note();
1734 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1736 // keep notes in standard midi range
1737 clamp_to_0_127(new_pitch);
1739 // keep original pitch if note is dragged outside valid midi range
1740 if ((original_pitch != 0 && new_pitch == 0)
1741 || (original_pitch != 127 && new_pitch == 127)) {
1742 new_pitch = original_pitch;
1745 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1746 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1748 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1753 // care about notes being moved beyond the upper/lower bounds on the canvas
1754 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1755 highest_note_in_selection > midi_stream_view()->highest_note()) {
1756 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1761 MidiRegionView::snap_pixel_to_frame(double x)
1763 PublicEditor& editor = trackview.editor();
1764 // x is region relative, convert it to global absolute frames
1765 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1766 editor.snap_to(frame);
1767 return frame - _region->position(); // convert back to region relative
1771 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1773 PublicEditor& editor = trackview.editor();
1774 // x is region relative, convert it to global absolute frames
1775 nframes64_t frame = x + _region->position();
1776 editor.snap_to(frame);
1777 return frame - _region->position(); // convert back to region relative
1781 MidiRegionView::snap_to_pixel(double x)
1783 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1787 MidiRegionView::get_position_pixels()
1789 nframes64_t region_frame = get_position();
1790 return trackview.editor().frame_to_pixel(region_frame);
1794 MidiRegionView::get_end_position_pixels()
1796 nframes64_t frame = get_position() + get_duration ();
1797 return trackview.editor().frame_to_pixel(frame);
1801 MidiRegionView::beats_to_frames(double beats) const
1803 return _time_converter.to(beats);
1807 MidiRegionView::frames_to_beats(nframes64_t frames) const
1809 return _time_converter.from(frames);
1813 MidiRegionView::begin_resizing (bool /*at_front*/)
1815 _resize_data.clear();
1817 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1818 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1820 // only insert CanvasNotes into the map
1822 NoteResizeData *resize_data = new NoteResizeData();
1823 resize_data->canvas_note = note;
1825 // create a new SimpleRect from the note which will be the resize preview
1826 SimpleRect *resize_rect = new SimpleRect(
1827 *group, note->x1(), note->y1(), note->x2(), note->y2());
1829 // calculate the colors: get the color settings
1830 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1831 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1834 // make the resize preview notes more transparent and bright
1835 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1837 // calculate color based on note velocity
1838 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1839 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1843 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1844 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1846 resize_data->resize_rect = resize_rect;
1847 _resize_data.push_back(resize_data);
1853 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1855 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1856 SimpleRect* resize_rect = (*i)->resize_rect;
1857 CanvasNote* canvas_note = (*i)->canvas_note;
1862 current_x = canvas_note->x1() + delta_x;
1864 // x is in track relative, transform it to region relative
1865 current_x = delta_x - get_position_pixels();
1869 current_x = canvas_note->x2() + delta_x;
1871 // x is in track relative, transform it to region relative
1872 current_x = delta_x - get_end_position_pixels ();
1877 resize_rect->property_x1() = snap_to_pixel(current_x);
1878 resize_rect->property_x2() = canvas_note->x2();
1880 resize_rect->property_x2() = snap_to_pixel(current_x);
1881 resize_rect->property_x1() = canvas_note->x1();
1887 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1889 start_diff_command(_("resize notes"));
1891 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1892 CanvasNote* canvas_note = (*i)->canvas_note;
1893 SimpleRect* resize_rect = (*i)->resize_rect;
1894 const double region_start = get_position_pixels();
1899 current_x = canvas_note->x1() + delta_x;
1901 // x is in track relative, transform it to region relative
1902 current_x = region_start + delta_x;
1906 current_x = canvas_note->x2() + delta_x;
1908 // x is in track relative, transform it to region relative
1909 current_x = region_start + delta_x;
1913 current_x = snap_pixel_to_frame (current_x);
1914 current_x = frames_to_beats (current_x);
1916 if (at_front && current_x < canvas_note->note()->end_time()) {
1917 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1919 double len = canvas_note->note()->time() - current_x;
1920 len += canvas_note->note()->length();
1923 /* XXX convert to beats */
1924 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1929 double len = current_x - canvas_note->note()->time();
1932 /* XXX convert to beats */
1933 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1941 _resize_data.clear();
1946 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1948 uint8_t new_velocity;
1951 new_velocity = event->note()->velocity() + velocity;
1952 clamp_to_0_127(new_velocity);
1954 new_velocity = velocity;
1957 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1961 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1966 new_note = event->note()->note() + note;
1971 clamp_to_0_127 (new_note);
1972 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1976 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1978 bool change_start = false;
1979 bool change_length = false;
1980 Evoral::MusicalTime new_start;
1981 Evoral::MusicalTime new_length;
1983 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1985 front_delta: if positive - move the start of the note later in time (shortening it)
1986 if negative - move the start of the note earlier in time (lengthening it)
1988 end_delta: if positive - move the end of the note later in time (lengthening it)
1989 if negative - move the end of the note earlier in time (shortening it)
1993 if (front_delta < 0) {
1995 if (event->note()->time() < -front_delta) {
1998 new_start = event->note()->time() + front_delta; // moves earlier
2001 /* start moved toward zero, so move the end point out to where it used to be.
2002 Note that front_delta is negative, so this increases the length.
2005 new_length = event->note()->length() - front_delta;
2006 change_start = true;
2007 change_length = true;
2011 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2013 if (new_pos < event->note()->end_time()) {
2014 new_start = event->note()->time() + front_delta;
2015 /* start moved toward the end, so move the end point back to where it used to be */
2016 new_length = event->note()->length() - front_delta;
2017 change_start = true;
2018 change_length = true;
2025 bool can_change = true;
2026 if (end_delta < 0) {
2027 if (event->note()->length() < -end_delta) {
2033 new_length = event->note()->length() + end_delta;
2034 change_length = true;
2039 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2042 if (change_length) {
2043 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2048 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2050 Evoral::MusicalTime new_time;
2054 if (event->note()->time() < -delta) {
2057 new_time = event->note()->time() + delta;
2060 new_time = event->note()->time() + delta;
2066 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2070 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2074 if (_selection.empty()) {
2089 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2090 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2096 start_diff_command(_("change velocities"));
2098 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2099 Selection::iterator next = i;
2101 change_note_velocity (*i, delta, true);
2110 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2112 if (_selection.empty()) {
2129 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2131 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2135 if ((int8_t) (*i)->note()->note() + delta > 127) {
2142 start_diff_command (_("transpose"));
2144 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2145 Selection::iterator next = i;
2147 change_note_note (*i, delta, true);
2155 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2157 Evoral::MusicalTime delta;
2162 /* grab the current grid distance */
2164 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2166 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2167 cerr << "Grid type not available as beats - TO BE FIXED\n";
2176 start_diff_command (_("change note lengths"));
2178 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2179 Selection::iterator next = i;
2182 /* note the negation of the delta for start */
2184 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2193 MidiRegionView::nudge_notes (bool forward)
2195 if (_selection.empty()) {
2199 /* pick a note as the point along the timeline to get the nudge distance.
2200 its not necessarily the earliest note, so we may want to pull the notes out
2201 into a vector and sort before using the first one.
2204 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2206 nframes64_t distance;
2208 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2210 /* grid is off - use nudge distance */
2212 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2218 nframes64_t next_pos = ref_point;
2221 /* XXX need check on max_frames, but that needs max_frames64 or something */
2224 if (next_pos == 0) {
2230 cerr << "ref point was " << ref_point << " next was " << next_pos;
2231 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2232 distance = ref_point - next_pos;
2233 cerr << " final is " << next_pos << " distance = " << distance << endl;
2236 if (distance == 0) {
2240 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2246 start_diff_command (_("nudge"));
2248 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2249 Selection::iterator next = i;
2251 change_note_time (*i, delta, true);
2259 MidiRegionView::change_channel(uint8_t channel)
2261 start_diff_command(_("change channel"));
2262 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2263 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2270 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2272 if (_mouse_state == SelectTouchDragging) {
2273 note_selected(ev, true);
2277 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2278 // This causes an infinite loop on note add sometimes
2279 //PublicEditor& editor (trackview.editor());
2280 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2281 //editor.show_verbose_canvas_cursor_with (buf);
2285 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2287 PublicEditor& editor (trackview.editor());
2288 editor.hide_verbose_canvas_cursor ();
2293 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2295 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2297 display_model(msrc->model());
2301 MidiRegionView::set_frame_color()
2304 if (_selected && should_show_selection) {
2305 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2307 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2313 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2317 case FilterChannels:
2318 _force_channel = -1;
2321 _force_channel = mask;
2322 mask = 0xFFFF; // Show all notes as active (below)
2325 // Update notes for selection
2326 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2327 (*i)->on_channel_selection_change(mask);
2330 _last_channel_selection = mask;
2334 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2336 _model_name = model;
2337 _custom_device_mode = custom_device_mode;
2342 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2344 if (_selection.empty()) {
2348 PublicEditor& editor (trackview.editor());
2353 editor.get_cut_buffer().add (selection_as_cut_buffer());
2359 start_delta_command();
2361 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2366 delta_remove_note (*i);
2377 MidiRegionView::selection_as_cut_buffer () const
2381 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2382 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2385 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2392 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2398 start_delta_command (_("paste"));
2400 Evoral::MusicalTime beat_delta;
2401 Evoral::MusicalTime paste_pos_beats;
2402 Evoral::MusicalTime duration;
2403 Evoral::MusicalTime end_point;
2405 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2406 paste_pos_beats = frames_to_beats (pos - _region->position());
2407 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2408 paste_pos_beats = 0;
2410 _selection.clear ();
2412 for (int n = 0; n < (int) times; ++n) {
2414 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2416 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2417 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2419 /* make all newly added notes selected */
2421 delta_add_note (copied_note, true);
2422 end_point = copied_note->end_time();
2425 paste_pos_beats += duration;
2428 /* if we pasted past the current end of the region, extend the region */
2430 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2431 nframes64_t region_end = _region->position() + _region->length() - 1;
2433 if (end_frame > region_end) {
2435 trackview.session().begin_reversible_command (_("paste"));
2437 XMLNode& before (_region->get_state());
2438 _region->set_length (end_frame, this);
2439 trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2445 struct EventNoteTimeEarlyFirstComparator {
2446 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2447 return a->note()->time() < b->note()->time();
2452 MidiRegionView::time_sort_events ()
2454 if (!_sort_needed) {
2458 EventNoteTimeEarlyFirstComparator cmp;
2461 _sort_needed = false;
2465 MidiRegionView::goto_next_note ()
2467 // nframes64_t pos = -1;
2468 bool use_next = false;
2470 if (_events.back()->selected()) {
2474 time_sort_events ();
2476 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2477 if ((*i)->selected()) {
2480 } else if (use_next) {
2482 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2487 /* use the first one */
2489 unique_select (_events.front());
2494 MidiRegionView::goto_previous_note ()
2496 // nframes64_t pos = -1;
2497 bool use_next = false;
2499 if (_events.front()->selected()) {
2503 time_sort_events ();
2505 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2506 if ((*i)->selected()) {
2509 } else if (use_next) {
2511 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2516 /* use the last one */
2518 unique_select (*(_events.rbegin()));
2522 MidiRegionView::selection_as_notelist (Notes& selected)
2524 time_sort_events ();
2526 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2527 if ((*i)->selected()) {
2528 selected.insert ((*i)->note());