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"
41 #include "evoral/Parameter.hpp"
42 #include "evoral/Control.hpp"
44 #include "automation_region_view.h"
45 #include "automation_time_axis.h"
46 #include "canvas-hit.h"
47 #include "canvas-note.h"
48 #include "canvas-program-change.h"
49 #include "ghostregion.h"
50 #include "gui_thread.h"
52 #include "midi_cut_buffer.h"
53 #include "midi_list_editor.h"
54 #include "midi_region_view.h"
55 #include "midi_streamview.h"
56 #include "midi_time_axis.h"
57 #include "midi_time_axis.h"
58 #include "midi_util.h"
59 #include "public_editor.h"
60 #include "selection.h"
61 #include "simpleline.h"
62 #include "streamview.h"
68 using namespace ARDOUR;
70 using namespace Editing;
71 using namespace ArdourCanvas;
73 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
74 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
75 : RegionView (parent, tv, r, spu, basic_color)
77 , _last_channel_selection(0xFFFF)
78 , _default_note_length(1.0)
79 , _current_range_min(0)
80 , _current_range_max(0)
81 , _model_name(string())
82 , _custom_device_mode(string())
84 , _note_group(new ArdourCanvas::Group(*parent))
90 , _optimization_iterator (_events.end())
92 _note_group->raise_to_top();
95 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
96 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
97 TimeAxisViewItem::Visibility visibility)
98 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
100 , _last_channel_selection(0xFFFF)
101 , _default_note_length(1.0)
102 , _model_name(string())
103 , _custom_device_mode(string())
105 , _note_group(new ArdourCanvas::Group(*parent))
110 , _sort_needed (true)
111 , _optimization_iterator (_events.end())
114 _note_group->raise_to_top();
118 MidiRegionView::MidiRegionView (const MidiRegionView& other)
119 : sigc::trackable(other)
122 , _last_channel_selection(0xFFFF)
123 , _default_note_length(1.0)
124 , _model_name(string())
125 , _custom_device_mode(string())
127 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
132 , _sort_needed (true)
133 , _optimization_iterator (_events.end())
138 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
139 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
144 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
145 : RegionView (other, boost::shared_ptr<Region> (region))
147 , _last_channel_selection(0xFFFF)
148 , _default_note_length(1.0)
149 , _model_name(string())
150 , _custom_device_mode(string())
152 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
157 , _sort_needed (true)
158 , _optimization_iterator (_events.end())
163 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
164 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
170 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
173 midi_region()->midi_source(0)->load_model();
176 _model = midi_region()->midi_source(0)->model();
177 _enable_display = false;
179 RegionView::init (basic_color, false);
181 compute_colors (basic_color);
183 set_height (trackview.current_height());
186 region_sync_changed ();
187 region_resized (BoundsChanged);
190 reset_width_dependent_items (_pixel_width);
194 _enable_display = true;
197 display_model (_model);
201 group->raise_to_top();
202 group->signal_event().connect (mem_fun (this, &MidiRegionView::canvas_event), false);
204 midi_view()->signal_channel_mode_changed().connect(
205 mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
207 midi_view()->signal_midi_patch_settings_changed().connect(
208 mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
212 MidiRegionView::canvas_event(GdkEvent* ev)
214 PublicEditor& editor (trackview.editor());
216 if (!editor.internal_editing()) {
220 static double drag_start_x, drag_start_y;
221 static double last_x, last_y;
222 double event_x, event_y;
223 nframes64_t event_frame = 0;
226 static ArdourCanvas::SimpleRect* drag_rect = 0;
228 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
229 to its items, which means that ev->type == GDK_SCROLL will never be seen
234 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
236 if (ev->scroll.direction == GDK_SCROLL_UP) {
237 change_velocities (true, fine, false);
239 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
240 change_velocities (false, fine, false);
249 /* since GTK bindings are generally activated on press, and since
250 detectable auto-repeat is the name of the game and only sends
251 repeated presses, carry out key actions at key press, not release.
254 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
255 _mouse_state = SelectTouchDragging;
258 } else if (ev->key.keyval == GDK_Escape) {
262 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
264 bool start = (ev->key.keyval == GDK_comma);
265 bool end = (ev->key.keyval == GDK_period);
266 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
267 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
269 change_note_lengths (fine, shorter, start, end);
273 } else if (ev->key.keyval == GDK_Delete) {
278 } else if (ev->key.keyval == GDK_Tab) {
280 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
281 goto_previous_note ();
287 } else if (ev->key.keyval == GDK_Up) {
289 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
290 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
292 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
293 change_velocities (true, fine, allow_smush);
295 transpose (true, fine, allow_smush);
299 } else if (ev->key.keyval == GDK_Down) {
301 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
302 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
304 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
305 change_velocities (false, fine, allow_smush);
307 transpose (false, fine, allow_smush);
311 } else if (ev->key.keyval == GDK_Left) {
316 } else if (ev->key.keyval == GDK_Right) {
321 } else if (ev->key.keyval == GDK_Control_L) {
327 case GDK_KEY_RELEASE:
328 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
334 case GDK_BUTTON_PRESS:
335 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
336 _pressed_button = ev->button.button;
337 _mouse_state = Pressed;
340 _pressed_button = ev->button.button;
343 case GDK_2BUTTON_PRESS:
346 case GDK_ENTER_NOTIFY:
347 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
348 Keyboard::magic_widget_grab_focus();
352 case GDK_MOTION_NOTIFY:
353 event_x = ev->motion.x;
354 event_y = ev->motion.y;
355 group->w2i(event_x, event_y);
357 // convert event_x to global frame
358 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
359 trackview.editor().snap_to(event_frame);
360 // convert event_frame back to local coordinates relative to position
361 event_frame -= _region->position();
363 switch (_mouse_state) {
364 case Pressed: // Drag start
367 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
368 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
369 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
372 drag_start_x = event_x;
373 drag_start_y = event_y;
375 drag_rect = new ArdourCanvas::SimpleRect(*group);
376 drag_rect->property_x1() = event_x;
377 drag_rect->property_y1() = event_y;
378 drag_rect->property_x2() = event_x;
379 drag_rect->property_y2() = event_y;
380 drag_rect->property_outline_what() = 0xFF;
381 drag_rect->property_outline_color_rgba()
382 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
383 drag_rect->property_fill_color_rgba()
384 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
386 _mouse_state = SelectRectDragging;
389 // Add note drag start
390 } else if (editor.current_mouse_mode() == MouseRange) {
391 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
392 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
395 drag_start_x = event_x;
396 drag_start_y = event_y;
398 drag_rect = new ArdourCanvas::SimpleRect(*group);
399 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
401 drag_rect->property_y1() = midi_stream_view()->note_to_y(
402 midi_stream_view()->y_to_note(event_y));
403 drag_rect->property_x2() = event_x;
404 drag_rect->property_y2() = drag_rect->property_y1()
405 + floor(midi_stream_view()->note_height());
406 drag_rect->property_outline_what() = 0xFF;
407 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
408 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
410 _mouse_state = AddDragging;
416 case SelectRectDragging: // Select drag motion
417 case AddDragging: // Add note drag motion
418 if (ev->motion.is_hint) {
421 GdkModifierType state;
422 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
427 if (_mouse_state == AddDragging)
428 event_x = trackview.editor().frame_to_pixel(event_frame);
431 if (event_x > drag_start_x)
432 drag_rect->property_x2() = event_x;
434 drag_rect->property_x1() = event_x;
437 if (drag_rect && _mouse_state == SelectRectDragging) {
438 if (event_y > drag_start_y)
439 drag_rect->property_y2() = event_y;
441 drag_rect->property_y1() = event_y;
443 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
449 case SelectTouchDragging:
457 case GDK_BUTTON_RELEASE:
458 event_x = ev->motion.x;
459 event_y = ev->motion.y;
460 group->w2i(event_x, event_y);
461 group->ungrab(ev->button.time);
462 event_frame = trackview.editor().pixel_to_frame(event_x);
464 if (ev->button.button == 3) {
466 } else if (_pressed_button != 1) {
470 switch (_mouse_state) {
471 case Pressed: // Clicked
472 switch (editor.current_mouse_mode()) {
478 create_note_at(event_x, event_y, _default_note_length);
485 case SelectRectDragging: // Select drag done
490 case AddDragging: // Add drag done
492 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
493 const double x = drag_rect->property_x1();
494 const double length = trackview.editor().pixel_to_frame(
495 drag_rect->property_x2() - drag_rect->property_x1());
497 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
512 MidiRegionView::show_list_editor ()
514 MidiListEditor* mle = new MidiListEditor (trackview.session(), midi_region());
518 /** Add a note to the model, and the view, at a canvas (click) coordinate.
519 * \param x horizontal position in pixels
520 * \param y vertical position in pixels
521 * \param length duration of the note in beats */
523 MidiRegionView::create_note_at(double x, double y, double length)
525 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
526 MidiStreamView* const view = mtv->midi_view();
528 double note = midi_stream_view()->y_to_note(y);
531 assert(note <= 127.0);
533 // Start of note in frames relative to region start
534 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
535 assert(start_frames >= 0);
538 length = frames_to_beats(
539 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
541 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
542 frames_to_beats(start_frames + _region->start()), length,
543 (uint8_t)note, 0x40));
545 view->update_note_range(new_note->note());
547 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
549 _model->apply_command(trackview.session(), cmd);
551 play_midi_note (new_note);
555 MidiRegionView::clear_events()
560 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
561 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
566 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
571 _pgm_changes.clear();
573 _optimization_iterator = _events.end();
578 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
581 content_connection.disconnect ();
582 content_connection = _model->ContentsChanged.connect(sigc::mem_fun(this, &MidiRegionView::redisplay_model));
584 if (_enable_display) {
591 MidiRegionView::start_delta_command(string name)
593 if (!_delta_command) {
594 _delta_command = _model->new_delta_command(name);
599 MidiRegionView::start_diff_command(string name)
601 if (!_diff_command) {
602 _diff_command = _model->new_diff_command(name);
607 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
609 if (_delta_command) {
610 _delta_command->add(note);
613 _marked_for_selection.insert(note);
616 _marked_for_velocity.insert(note);
621 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
623 if (_delta_command && ev->note()) {
624 _delta_command->remove(ev->note());
629 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
630 MidiModel::DiffCommand::Property property,
634 _diff_command->change (ev->note(), property, val);
639 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
640 MidiModel::DiffCommand::Property property,
641 Evoral::MusicalTime val)
644 _diff_command->change (ev->note(), property, val);
649 MidiRegionView::apply_delta()
651 if (!_delta_command) {
655 // Mark all selected notes for selection when model reloads
656 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
657 _marked_for_selection.insert((*i)->note());
660 _model->apply_command(trackview.session(), _delta_command);
662 midi_view()->midi_track()->diskstream()->playlist_modified();
664 _marked_for_selection.clear();
665 _marked_for_velocity.clear();
669 MidiRegionView::apply_diff ()
671 if (!_diff_command) {
675 _model->apply_command(trackview.session(), _diff_command);
677 midi_view()->midi_track()->diskstream()->playlist_modified();
679 _marked_for_velocity.clear();
683 MidiRegionView::apply_delta_as_subcommand()
685 if (!_delta_command) {
689 // Mark all selected notes for selection when model reloads
690 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
691 _marked_for_selection.insert((*i)->note());
694 _model->apply_command_as_subcommand(trackview.session(), _delta_command);
696 midi_view()->midi_track()->diskstream()->playlist_modified();
698 _marked_for_selection.clear();
699 _marked_for_velocity.clear();
703 MidiRegionView::apply_diff_as_subcommand()
705 if (!_diff_command) {
709 // Mark all selected notes for selection when model reloads
710 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
711 _marked_for_selection.insert((*i)->note());
714 _model->apply_command_as_subcommand(trackview.session(), _diff_command);
716 midi_view()->midi_track()->diskstream()->playlist_modified();
718 _marked_for_selection.clear();
719 _marked_for_velocity.clear();
723 MidiRegionView::abort_command()
725 delete _delta_command;
727 delete _diff_command;
733 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
735 if (_optimization_iterator != _events.end()) {
736 ++_optimization_iterator;
739 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
740 return *_optimization_iterator;
743 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
744 if ((*_optimization_iterator)->note() == note) {
745 return *_optimization_iterator;
753 MidiRegionView::redisplay_model()
755 // Don't redisplay the model if we're currently recording and displaying that
761 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
765 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
771 MidiModel::Notes& notes (_model->notes());
772 _optimization_iterator = _events.begin();
774 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
776 boost::shared_ptr<NoteType> note (*n);
777 CanvasNoteEvent* cne;
779 if (note_in_visible_range (note)) {
781 if ((cne = find_canvas_note (note)) != 0) {
788 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
790 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
803 if ((cne = find_canvas_note (note)) != 0) {
810 /* remove note items that are no longer valid */
812 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
813 if (!(*i)->valid ()) {
815 i = _events.erase (i);
822 display_program_changes();
824 _model->read_unlock();
826 _marked_for_selection.clear ();
827 _marked_for_velocity.clear ();
829 /* we may have caused _events to contain things out of order (e.g. if a note
830 moved earlier or later). we don't generally need them in time order, but
831 make a note that a sort is required for those cases that require it.
838 MidiRegionView::display_program_changes()
840 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
845 Glib::Mutex::Lock lock (control->list()->lock());
847 uint8_t channel = control->parameter().channel();
849 for (AutomationList::const_iterator event = control->list()->begin();
850 event != control->list()->end(); ++event) {
851 double event_time = (*event)->when;
852 double program_number = floor((*event)->value + 0.5);
854 // Get current value of bank select MSB at time of the program change
855 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
856 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
858 if (msb_control != 0) {
859 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
862 // Get current value of bank select LSB at time of the program change
863 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
864 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
866 if (lsb_control != 0) {
867 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
870 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
872 boost::shared_ptr<MIDI::Name::Patch> patch =
873 MIDI::Name::MidiPatchManager::instance().find_patch(
874 _model_name, _custom_device_mode, channel, patch_key);
876 PCEvent program_change(event_time, uint8_t(program_number), channel);
879 add_pgm_change(program_change, patch->name());
882 snprintf(buf, 4, "%d", int(program_number));
883 add_pgm_change(program_change, buf);
889 MidiRegionView::display_sysexes()
891 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
892 Evoral::MusicalTime time = (*i)->time();
897 for (uint32_t b = 0; b < (*i)->size(); ++b) {
898 str << int((*i)->buffer()[b]);
899 if (b != (*i)->size() -1) {
903 string text = str.str();
905 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
907 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
909 double height = midi_stream_view()->contents_height();
911 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
912 new CanvasSysEx(*this, *group, text, height, x, 1.0));
914 // Show unless program change is beyond the region bounds
915 if (time - _region->start() >= _region->length() || time < _region->start()) {
921 _sys_exes.push_back(sysex);
926 MidiRegionView::~MidiRegionView ()
928 in_destructor = true;
930 RegionViewGoingAway (this); /* EMIT_SIGNAL */
939 delete _delta_command;
943 MidiRegionView::region_resized (Change what_changed)
945 RegionView::region_resized(what_changed);
947 if (what_changed & ARDOUR::PositionChanged) {
948 set_duration(_region->length(), 0);
949 if (_enable_display) {
956 MidiRegionView::reset_width_dependent_items (double pixel_width)
958 RegionView::reset_width_dependent_items(pixel_width);
959 assert(_pixel_width == pixel_width);
961 if (_enable_display) {
967 MidiRegionView::set_height (double height)
969 static const double FUDGE = 2.0;
970 const double old_height = _height;
971 RegionView::set_height(height);
972 _height = height - FUDGE;
974 apply_note_range(midi_stream_view()->lowest_note(),
975 midi_stream_view()->highest_note(),
976 height != old_height + FUDGE);
979 name_pixbuf->raise_to_top();
984 /** Apply the current note range from the stream view
985 * by repositioning/hiding notes as necessary
988 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
990 if (!_enable_display) {
994 if (!force && _current_range_min == min && _current_range_max == max) {
998 _current_range_min = min;
999 _current_range_max = max;
1001 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1002 CanvasNoteEvent* event = *i;
1003 boost::shared_ptr<NoteType> note (event->note());
1005 if (note->note() < _current_range_min ||
1006 note->note() > _current_range_max) {
1012 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1014 const double y1 = midi_stream_view()->note_to_y(note->note());
1015 const double y2 = y1 + floor(midi_stream_view()->note_height());
1017 cnote->property_y1() = y1;
1018 cnote->property_y2() = y2;
1020 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1022 double x = trackview.editor().frame_to_pixel(
1023 beats_to_frames(note->time()) - _region->start());
1024 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1025 double y = midi_stream_view()->note_to_y(event->note()->note())
1026 + ((diamond_size-2.0) / 4.0);
1028 chit->set_height (diamond_size);
1029 chit->move (x - chit->x1(), y - chit->y1());
1036 MidiRegionView::add_ghost (TimeAxisView& tv)
1040 double unit_position = _region->position () / samples_per_unit;
1041 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1042 MidiGhostRegion* ghost;
1044 if (mtv && mtv->midi_view()) {
1045 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1046 to allow having midi notes on top of note lines and waveforms.
1048 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1050 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1053 ghost->set_height ();
1054 ghost->set_duration (_region->length() / samples_per_unit);
1055 ghosts.push_back (ghost);
1057 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1058 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1059 ghost->add_note(note);
1063 ghost->GoingAway.connect (mem_fun(*this, &MidiRegionView::remove_ghost));
1069 /** Begin tracking note state for successive calls to add_event
1072 MidiRegionView::begin_write()
1074 assert(!_active_notes);
1075 _active_notes = new CanvasNote*[128];
1076 for (unsigned i=0; i < 128; ++i) {
1077 _active_notes[i] = 0;
1082 /** Destroy note state for add_event
1085 MidiRegionView::end_write()
1087 delete[] _active_notes;
1089 _marked_for_selection.clear();
1090 _marked_for_velocity.clear();
1094 /** Resolve an active MIDI note (while recording).
1097 MidiRegionView::resolve_note(uint8_t note, double end_time)
1099 if (midi_view()->note_mode() != Sustained) {
1103 if (_active_notes && _active_notes[note]) {
1104 const nframes64_t end_time_frames = beats_to_frames(end_time);
1105 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1106 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1107 _active_notes[note] = 0;
1112 /** Extend active notes to rightmost edge of region (if length is changed)
1115 MidiRegionView::extend_active_notes()
1117 if (!_active_notes) {
1121 for (unsigned i=0; i < 128; ++i) {
1122 if (_active_notes[i]) {
1123 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1129 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1131 if (!trackview.editor().sound_notes()) {
1135 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1138 route_ui->midi_track()->write_immediate_event(
1139 note->on_event().size(), note->on_event().buffer());
1141 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1142 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1143 * (1000 / (double)route_ui->session().nominal_frame_rate());
1144 Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1145 note_length_ms, G_PRIORITY_DEFAULT);
1149 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1151 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1154 route_ui->midi_track()->write_immediate_event(
1155 note->off_event().size(), note->off_event().buffer());
1161 MidiRegionView::note_in_visible_range(const boost::shared_ptr<NoteType> note) const
1163 const nframes64_t note_start_frames = beats_to_frames(note->time());
1164 bool outside = (note_start_frames - _region->start() >= _region->length())
1165 || (note_start_frames < _region->start())
1166 || (note->note() < midi_stream_view()->lowest_note())
1167 || (note->note() > midi_stream_view()->highest_note());
1172 MidiRegionView::update_note (CanvasNote* ev)
1174 boost::shared_ptr<NoteType> note = ev->note();
1176 const nframes64_t note_start_frames = beats_to_frames(note->time());
1177 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1179 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1182 const double y1 = midi_stream_view()->note_to_y(note->note());
1183 const double note_endpixel =
1184 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1186 ev->property_x1() = x;
1187 ev->property_y1() = y1;
1188 if (note->length() > 0) {
1189 ev->property_x2() = note_endpixel;
1191 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1193 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1195 if (note->length() == 0) {
1196 if (_active_notes) {
1197 assert(note->note() < 128);
1198 // If this note is already active there's a stuck note,
1199 // finish the old note rectangle
1200 if (_active_notes[note->note()]) {
1201 CanvasNote* const old_rect = _active_notes[note->note()];
1202 boost::shared_ptr<NoteType> old_note = old_rect->note();
1203 old_rect->property_x2() = x;
1204 old_rect->property_outline_what() = (guint32) 0xF;
1206 _active_notes[note->note()] = ev;
1208 /* outline all but right edge */
1209 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1211 /* outline all edges */
1212 ev->property_outline_what() = (guint32) 0xF;
1217 MidiRegionView::update_hit (CanvasHit* ev)
1219 boost::shared_ptr<NoteType> note = ev->note();
1221 const nframes64_t note_start_frames = beats_to_frames(note->time());
1222 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1223 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1224 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1229 /** Add a MIDI note to the view (with length).
1231 * If in sustained mode, notes with length 0 will be considered active
1232 * notes, and resolve_note should be called when the corresponding note off
1233 * event arrives, to properly display the note.
1236 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
1238 CanvasNoteEvent* event = 0;
1240 assert(note->time() >= 0);
1241 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1243 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1245 if (midi_view()->note_mode() == Sustained) {
1247 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1249 update_note (ev_rect);
1253 MidiGhostRegion* gr;
1255 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1256 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1257 gr->add_note(ev_rect);
1261 } else if (midi_view()->note_mode() == Percussive) {
1263 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1265 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1267 update_hit (ev_diamond);
1276 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1277 note_selected(event, true);
1280 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1281 event->show_velocity();
1283 event->on_channel_selection_change(_last_channel_selection);
1284 _events.push_back(event);
1286 if (note_in_visible_range(note)) {
1295 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1296 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1298 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1300 start_delta_command (_("step add"));
1301 delta_add_note (new_note, true, false);
1304 /* potentially extend region to hold new note */
1306 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1307 nframes64_t region_end = _region->position() + _region->length() - 1;
1309 if (end_frame > region_end) {
1310 _region->set_length (end_frame, this);
1317 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1319 assert(program.time >= 0);
1321 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1322 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1324 double height = midi_stream_view()->contents_height();
1326 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1327 new CanvasProgramChange(*this, *group,
1332 _custom_device_mode,
1333 program.time, program.channel, program.value));
1335 // Show unless program change is beyond the region bounds
1336 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1342 _pgm_changes.push_back(pgm_change);
1346 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1348 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1349 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1350 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1352 if (msb_control != 0) {
1353 msb = int(msb_control->get_float(true, time));
1354 cerr << "got msb " << msb;
1357 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1358 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1360 if (lsb_control != 0) {
1361 lsb = lsb_control->get_float(true, time);
1362 cerr << " got lsb " << lsb;
1365 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1366 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1367 float program_number = -1.0;
1368 if (program_control != 0) {
1369 program_number = program_control->get_float(true, time);
1370 cerr << " got program " << program_number << endl;
1373 key.msb = (int) floor(msb + 0.5);
1374 key.lsb = (int) floor(lsb + 0.5);
1375 key.program_number = (int) floor(program_number + 0.5);
1376 assert(key.is_sane());
1381 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1383 // TODO: Get the real event here and alter them at the original times
1384 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1385 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1386 if (msb_control != 0) {
1387 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1390 // TODO: Get the real event here and alter them at the original times
1391 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1392 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1393 if (lsb_control != 0) {
1394 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1397 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1398 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1400 assert(program_control != 0);
1401 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1407 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1409 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1410 alter_program_change(program_change_event, new_patch);
1414 MidiRegionView::previous_program(CanvasProgramChange& program)
1416 MIDI::Name::PatchPrimaryKey key;
1417 get_patch_key_at(program.event_time(), program.channel(), key);
1419 boost::shared_ptr<MIDI::Name::Patch> patch =
1420 MIDI::Name::MidiPatchManager::instance().previous_patch(
1422 _custom_device_mode,
1426 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1428 alter_program_change(program_change_event, patch->patch_primary_key());
1433 MidiRegionView::next_program(CanvasProgramChange& program)
1435 MIDI::Name::PatchPrimaryKey key;
1436 get_patch_key_at(program.event_time(), program.channel(), key);
1438 boost::shared_ptr<MIDI::Name::Patch> patch =
1439 MIDI::Name::MidiPatchManager::instance().next_patch(
1441 _custom_device_mode,
1445 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1447 alter_program_change(program_change_event, patch->patch_primary_key());
1452 MidiRegionView::delete_selection()
1454 if (_selection.empty()) {
1458 start_delta_command (_("delete selection"));
1460 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1461 if ((*i)->selected()) {
1462 _delta_command->remove((*i)->note());
1472 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1474 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1475 if ((*i)->selected() && (*i) != ev) {
1476 (*i)->selected(false);
1477 (*i)->hide_velocity();
1485 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1487 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1489 Selection::iterator tmp = i;
1493 remove_from_selection (*i);
1499 if (!ev->selected()) {
1500 add_to_selection (ev);
1505 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1508 clear_selection_except(ev);
1513 if (!ev->selected()) {
1514 add_to_selection (ev);
1518 /* find end of latest note selected, select all between that and the start of "ev" */
1520 Evoral::MusicalTime earliest = DBL_MAX;
1521 Evoral::MusicalTime latest = 0;
1523 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1524 if ((*i)->note()->end_time() > latest) {
1525 latest = (*i)->note()->end_time();
1527 if ((*i)->note()->time() < earliest) {
1528 earliest = (*i)->note()->time();
1532 if (ev->note()->end_time() > latest) {
1533 latest = ev->note()->end_time();
1536 if (ev->note()->time() < earliest) {
1537 earliest = ev->note()->time();
1540 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1542 /* find notes entirely within OR spanning the earliest..latest range */
1544 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1545 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1546 add_to_selection (*i);
1550 /* if events were guaranteed to be time sorted, we could do this.
1551 but as of sept 10th 2009, they no longer are.
1554 if ((*i)->note()->time() > latest) {
1563 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1565 remove_from_selection (ev);
1569 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1579 // TODO: Make this faster by storing the last updated selection rect, and only
1580 // adjusting things that are in the area that appears/disappeared.
1581 // We probably need a tree to be able to find events in O(log(n)) time.
1583 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1585 /* check if any corner of the note is inside the rect
1588 1) this is computing "touched by", not "contained by" the rect.
1589 2) this does not require that events be sorted in time.
1592 const double ix1 = (*i)->x1();
1593 const double ix2 = (*i)->x2();
1594 const double iy1 = (*i)->y1();
1595 const double iy2 = (*i)->y2();
1597 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1598 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1599 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1600 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1603 if (!(*i)->selected()) {
1604 add_to_selection (*i);
1606 } else if ((*i)->selected()) {
1607 // Not inside rectangle
1608 remove_from_selection (*i);
1614 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1616 Selection::iterator i = _selection.find (ev);
1618 if (i != _selection.end()) {
1619 _selection.erase (i);
1622 ev->selected (false);
1623 ev->hide_velocity ();
1625 if (_selection.empty()) {
1626 PublicEditor& editor (trackview.editor());
1627 editor.get_selection().remove (this);
1632 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1634 bool add_mrv_selection = false;
1636 if (_selection.empty()) {
1637 add_mrv_selection = true;
1640 if (_selection.insert (ev).second) {
1641 ev->selected (true);
1642 play_midi_note ((ev)->note());
1645 if (add_mrv_selection) {
1646 PublicEditor& editor (trackview.editor());
1647 editor.get_selection().add (this);
1652 MidiRegionView::move_selection(double dx, double dy)
1654 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1655 (*i)->move_event(dx, dy);
1660 MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
1662 // TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy...
1663 if (_selection.find(ev) == _selection.end()) {
1667 uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note();
1668 uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
1669 uint8_t highest_note_difference = 0;
1671 // find highest and lowest notes first
1672 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1673 uint8_t pitch = (*i)->note()->note();
1674 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1675 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1679 cerr << "dnote: " << (int) dnote << endl;
1680 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1681 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1682 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1683 << int(highest_note_in_selection) << endl;
1684 cerr << "selection size: " << _selection.size() << endl;
1685 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1688 // Make sure the note pitch does not exceed the MIDI standard range
1689 if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
1690 highest_note_difference = highest_note_in_selection - 127;
1693 start_diff_command(_("move notes"));
1695 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1697 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1700 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1702 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1705 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1711 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1713 uint8_t original_pitch = (*i)->note()->note();
1714 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1716 // keep notes in standard midi range
1717 clamp_to_0_127(new_pitch);
1719 // keep original pitch if note is dragged outside valid midi range
1720 if ((original_pitch != 0 && new_pitch == 0)
1721 || (original_pitch != 127 && new_pitch == 127)) {
1722 new_pitch = original_pitch;
1725 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1726 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1728 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1733 // care about notes being moved beyond the upper/lower bounds on the canvas
1734 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1735 highest_note_in_selection > midi_stream_view()->highest_note()) {
1736 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1741 MidiRegionView::snap_pixel_to_frame(double x)
1743 PublicEditor& editor = trackview.editor();
1744 // x is region relative, convert it to global absolute frames
1745 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1746 editor.snap_to(frame);
1747 return frame - _region->position(); // convert back to region relative
1751 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1753 PublicEditor& editor = trackview.editor();
1754 // x is region relative, convert it to global absolute frames
1755 nframes64_t frame = x + _region->position();
1756 editor.snap_to(frame);
1757 return frame - _region->position(); // convert back to region relative
1761 MidiRegionView::snap_to_pixel(double x)
1763 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1767 MidiRegionView::get_position_pixels()
1769 nframes64_t region_frame = get_position();
1770 return trackview.editor().frame_to_pixel(region_frame);
1774 MidiRegionView::get_end_position_pixels()
1776 nframes64_t frame = get_position() + get_duration ();
1777 return trackview.editor().frame_to_pixel(frame);
1781 MidiRegionView::beats_to_frames(double beats) const
1783 return _time_converter.to(beats);
1787 MidiRegionView::frames_to_beats(nframes64_t frames) const
1789 return _time_converter.from(frames);
1793 MidiRegionView::begin_resizing(bool at_front)
1795 _resize_data.clear();
1797 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1798 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1800 // only insert CanvasNotes into the map
1802 NoteResizeData *resize_data = new NoteResizeData();
1803 resize_data->canvas_note = note;
1805 // create a new SimpleRect from the note which will be the resize preview
1806 SimpleRect *resize_rect = new SimpleRect(
1807 *group, note->x1(), note->y1(), note->x2(), note->y2());
1809 // calculate the colors: get the color settings
1810 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1811 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1814 // make the resize preview notes more transparent and bright
1815 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1817 // calculate color based on note velocity
1818 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1819 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1823 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1824 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1826 resize_data->resize_rect = resize_rect;
1827 _resize_data.push_back(resize_data);
1833 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1835 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1836 SimpleRect* resize_rect = (*i)->resize_rect;
1837 CanvasNote* canvas_note = (*i)->canvas_note;
1842 current_x = canvas_note->x1() + delta_x;
1844 // x is in track relative, transform it to region relative
1845 current_x = delta_x - get_position_pixels();
1849 current_x = canvas_note->x2() + delta_x;
1851 // x is in track relative, transform it to region relative
1852 current_x = delta_x - get_end_position_pixels ();
1857 resize_rect->property_x1() = snap_to_pixel(current_x);
1858 resize_rect->property_x2() = canvas_note->x2();
1860 resize_rect->property_x2() = snap_to_pixel(current_x);
1861 resize_rect->property_x1() = canvas_note->x1();
1867 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1869 start_diff_command(_("resize notes"));
1871 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1872 CanvasNote* canvas_note = (*i)->canvas_note;
1873 SimpleRect* resize_rect = (*i)->resize_rect;
1874 const double region_start = get_position_pixels();
1879 current_x = canvas_note->x1() + delta_x;
1881 // x is in track relative, transform it to region relative
1882 current_x = region_start + delta_x;
1886 current_x = canvas_note->x2() + delta_x;
1888 // x is in track relative, transform it to region relative
1889 current_x = region_start + delta_x;
1893 current_x = snap_pixel_to_frame (current_x);
1894 current_x = frames_to_beats (current_x);
1896 if (at_front && current_x < canvas_note->note()->end_time()) {
1897 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1901 double len = current_x - canvas_note->note()->time();
1904 /* XXX convert to beats */
1905 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1913 _resize_data.clear();
1918 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1920 uint8_t new_velocity;
1923 new_velocity = event->note()->velocity() + velocity;
1924 clamp_to_0_127(new_velocity);
1926 new_velocity = velocity;
1929 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1933 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1938 new_note = event->note()->note() + note;
1943 clamp_to_0_127 (new_note);
1944 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1948 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1950 bool change_start = false;
1951 bool change_length = false;
1952 Evoral::MusicalTime new_start;
1953 Evoral::MusicalTime new_length;
1955 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1957 front_delta: if positive - move the start of the note later in time (shortening it)
1958 if negative - move the start of the note earlier in time (lengthening it)
1960 end_delta: if positive - move the end of the note later in time (lengthening it)
1961 if negative - move the end of the note earlier in time (shortening it)
1965 if (front_delta < 0) {
1967 if (event->note()->time() < -front_delta) {
1970 new_start = event->note()->time() + front_delta; // moves earlier
1973 /* start moved toward zero, so move the end point out to where it used to be.
1974 Note that front_delta is negative, so this increases the length.
1977 new_length = event->note()->length() - front_delta;
1978 change_start = true;
1979 change_length = true;
1983 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
1985 if (new_pos < event->note()->end_time()) {
1986 new_start = event->note()->time() + front_delta;
1987 /* start moved toward the end, so move the end point back to where it used to be */
1988 new_length = event->note()->length() - front_delta;
1989 change_start = true;
1990 change_length = true;
1997 bool can_change = true;
1998 if (end_delta < 0) {
1999 if (event->note()->length() < -end_delta) {
2005 new_length = event->note()->length() + end_delta;
2006 change_length = true;
2011 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2014 if (change_length) {
2015 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2020 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2022 Evoral::MusicalTime new_time;
2026 if (event->note()->time() < -delta) {
2029 new_time = event->note()->time() + delta;
2032 new_time = event->note()->time() + delta;
2038 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2042 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2046 if (_selection.empty()) {
2061 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2062 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2068 start_diff_command(_("change velocities"));
2070 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2071 Selection::iterator next = i;
2073 change_note_velocity (*i, delta, true);
2082 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2084 if (_selection.empty()) {
2101 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2103 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2107 if ((int8_t) (*i)->note()->note() + delta > 127) {
2114 start_diff_command (_("transpose"));
2116 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2117 Selection::iterator next = i;
2119 change_note_note (*i, delta, true);
2127 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2129 Evoral::MusicalTime delta;
2134 /* grab the current grid distance */
2136 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2138 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2139 cerr << "Grid type not available as beats - TO BE FIXED\n";
2148 start_diff_command (_("change note lengths"));
2150 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2151 Selection::iterator next = i;
2154 /* note the negation of the delta for start */
2156 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2165 MidiRegionView::nudge_notes (bool forward)
2167 if (_selection.empty()) {
2171 /* pick a note as the point along the timeline to get the nudge distance.
2172 its not necessarily the earliest note, so we may want to pull the notes out
2173 into a vector and sort before using the first one.
2176 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2178 nframes64_t distance;
2180 if ((distance = trackview.editor().get_nudge_distance (ref_point, unused)) == 0) {
2182 /* no nudge distance set - use grid */
2184 nframes64_t next_pos = ref_point;
2187 /* XXX need check on max_frames, but that needs max_frames64 or something */
2190 if (next_pos == 0) {
2196 cerr << "ref point was " << ref_point << " next was " << next_pos;
2197 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2198 distance = ref_point - next_pos;
2199 cerr << " final is " << next_pos << " distance = " << distance << endl;
2202 if (distance == 0) {
2206 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2212 start_diff_command (_("nudge"));
2214 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2215 Selection::iterator next = i;
2217 change_note_time (*i, delta, true);
2225 MidiRegionView::change_channel(uint8_t channel)
2227 start_diff_command(_("change channel"));
2228 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2229 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2236 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2238 if (_mouse_state == SelectTouchDragging) {
2239 note_selected(ev, true);
2242 PublicEditor& editor (trackview.editor());
2243 editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2247 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* ev)
2249 PublicEditor& editor (trackview.editor());
2250 editor.hide_verbose_canvas_cursor ();
2255 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2257 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2259 display_model(msrc->model());
2263 MidiRegionView::set_frame_color()
2266 if (_selected && should_show_selection) {
2267 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2269 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2275 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2279 case FilterChannels:
2280 _force_channel = -1;
2283 _force_channel = mask;
2284 mask = 0xFFFF; // Show all notes as active (below)
2287 // Update notes for selection
2288 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2289 (*i)->on_channel_selection_change(mask);
2292 _last_channel_selection = mask;
2296 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2298 _model_name = model;
2299 _custom_device_mode = custom_device_mode;
2304 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2306 if (_selection.empty()) {
2310 PublicEditor& editor (trackview.editor());
2315 editor.get_cut_buffer().add (selection_as_cut_buffer());
2321 start_delta_command();
2323 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2328 delta_remove_note (*i);
2339 MidiRegionView::selection_as_cut_buffer () const
2343 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2344 notes.push_back (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2347 /* sort them into time order */
2349 Evoral::Sequence<Evoral::MusicalTime>::LaterNoteComparator cmp;
2350 sort (notes.begin(), notes.end(), cmp);
2352 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2359 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2365 start_delta_command (_("paste"));
2367 Evoral::MusicalTime beat_delta;
2368 Evoral::MusicalTime paste_pos_beats;
2369 Evoral::MusicalTime duration;
2370 Evoral::MusicalTime end_point;
2372 duration = mcb.notes().back()->end_time() - mcb.notes().front()->time();
2373 paste_pos_beats = frames_to_beats (pos - _region->position());
2374 beat_delta = mcb.notes().front()->time() - paste_pos_beats;
2375 paste_pos_beats = 0;
2377 _selection.clear ();
2379 for (int n = 0; n < (int) times; ++n) {
2381 for (NoteList::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2383 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2384 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2386 /* make all newly added notes selected */
2388 delta_add_note (copied_note, true);
2389 end_point = copied_note->end_time();
2392 paste_pos_beats += duration;
2395 /* if we pasted past the current end of the region, extend the region */
2397 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2398 nframes64_t region_end = _region->position() + _region->length() - 1;
2400 if (end_frame > region_end) {
2402 trackview.session().begin_reversible_command (_("paste"));
2404 XMLNode& before (_region->get_state());
2405 _region->set_length (end_frame, this);
2406 trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2412 struct EventNoteTimeEarlyFirstComparator {
2413 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2414 return a->note()->time() < b->note()->time();
2419 MidiRegionView::time_sort_events ()
2421 if (!_sort_needed) {
2425 EventNoteTimeEarlyFirstComparator cmp;
2428 _sort_needed = false;
2432 MidiRegionView::goto_next_note ()
2434 // nframes64_t pos = -1;
2435 bool use_next = false;
2437 if (_events.back()->selected()) {
2441 time_sort_events ();
2443 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2444 if ((*i)->selected()) {
2447 } else if (use_next) {
2449 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2454 /* use the first one */
2456 unique_select (_events.front());
2461 MidiRegionView::goto_previous_note ()
2463 // nframes64_t pos = -1;
2464 bool use_next = false;
2466 if (_events.front()->selected()) {
2470 time_sort_events ();
2472 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2473 if ((*i)->selected()) {
2476 } else if (use_next) {
2478 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2483 /* use the last one */
2485 unique_select (*(_events.rbegin()));
2489 MidiRegionView::selection_as_notelist (NoteList& selected)
2491 time_sort_events ();
2493 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2494 if ((*i)->selected()) {
2495 selected.push_back ((*i)->note());