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"
68 using namespace ARDOUR;
70 using namespace Editing;
71 using namespace ArdourCanvas;
72 using Gtkmm2ext::Keyboard;
74 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
75 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
76 : RegionView (parent, tv, r, spu, basic_color)
78 , _last_channel_selection(0xFFFF)
79 , _default_note_length(1.0)
80 , _current_range_min(0)
81 , _current_range_max(0)
82 , _model_name(string())
83 , _custom_device_mode(string())
85 , _note_group(new ArdourCanvas::Group(*parent))
91 , _optimization_iterator (_events.end())
93 , no_sound_notes (false)
95 _note_group->raise_to_top();
98 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
99 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
100 TimeAxisViewItem::Visibility visibility)
101 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
103 , _last_channel_selection(0xFFFF)
104 , _default_note_length(1.0)
105 , _model_name(string())
106 , _custom_device_mode(string())
108 , _note_group(new ArdourCanvas::Group(*parent))
113 , _sort_needed (true)
114 , _optimization_iterator (_events.end())
116 , no_sound_notes (false)
119 _note_group->raise_to_top();
123 MidiRegionView::MidiRegionView (const MidiRegionView& other)
124 : sigc::trackable(other)
127 , _last_channel_selection(0xFFFF)
128 , _default_note_length(1.0)
129 , _model_name(string())
130 , _custom_device_mode(string())
132 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
137 , _sort_needed (true)
138 , _optimization_iterator (_events.end())
140 , no_sound_notes (false)
145 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
146 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
151 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
152 : RegionView (other, boost::shared_ptr<Region> (region))
154 , _last_channel_selection(0xFFFF)
155 , _default_note_length(1.0)
156 , _model_name(string())
157 , _custom_device_mode(string())
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
164 , _sort_needed (true)
165 , _optimization_iterator (_events.end())
167 , no_sound_notes (false)
172 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
173 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
179 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
182 midi_region()->midi_source(0)->load_model();
185 _model = midi_region()->midi_source(0)->model();
186 _enable_display = false;
188 RegionView::init (basic_color, false);
190 compute_colors (basic_color);
192 set_height (trackview.current_height());
195 region_sync_changed ();
196 region_resized (ARDOUR::bounds_change);
199 reset_width_dependent_items (_pixel_width);
203 _enable_display = true;
206 display_model (_model);
210 group->raise_to_top();
211 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
213 midi_view()->signal_channel_mode_changed().connect(
214 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
216 midi_view()->signal_midi_patch_settings_changed().connect(
217 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
221 MidiRegionView::canvas_event(GdkEvent* ev)
223 PublicEditor& editor (trackview.editor());
225 if (!editor.internal_editing()) {
229 static double drag_start_x, drag_start_y;
230 static double last_x, last_y;
231 double event_x, event_y;
232 nframes64_t event_frame = 0;
235 static ArdourCanvas::SimpleRect* drag_rect = 0;
237 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
238 to its items, which means that ev->type == GDK_SCROLL will never be seen
243 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
245 if (ev->scroll.direction == GDK_SCROLL_UP) {
246 change_velocities (true, fine, false);
248 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
249 change_velocities (false, fine, false);
258 /* since GTK bindings are generally activated on press, and since
259 detectable auto-repeat is the name of the game and only sends
260 repeated presses, carry out key actions at key press, not release.
263 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
264 _mouse_state = SelectTouchDragging;
267 } else if (ev->key.keyval == GDK_Escape) {
271 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
273 bool start = (ev->key.keyval == GDK_comma);
274 bool end = (ev->key.keyval == GDK_period);
275 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
276 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
278 change_note_lengths (fine, shorter, start, end);
282 } else if (ev->key.keyval == GDK_Delete) {
287 } else if (ev->key.keyval == GDK_Tab) {
289 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
290 goto_previous_note ();
296 } else if (ev->key.keyval == GDK_Up) {
298 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
299 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
301 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
302 change_velocities (true, fine, allow_smush);
304 transpose (true, fine, allow_smush);
308 } else if (ev->key.keyval == GDK_Down) {
310 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
311 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
313 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
314 change_velocities (false, fine, allow_smush);
316 transpose (false, fine, allow_smush);
320 } else if (ev->key.keyval == GDK_Left) {
325 } else if (ev->key.keyval == GDK_Right) {
330 } else if (ev->key.keyval == GDK_Control_L) {
333 } else if (ev->key.keyval == GDK_r) {
334 /* if we're not step editing, this really doesn't matter */
335 midi_view()->step_edit_rest ();
341 case GDK_KEY_RELEASE:
342 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
348 case GDK_BUTTON_PRESS:
349 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
350 _pressed_button = ev->button.button;
351 _mouse_state = Pressed;
354 _pressed_button = ev->button.button;
357 case GDK_2BUTTON_PRESS:
360 case GDK_ENTER_NOTIFY:
361 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
362 Keyboard::magic_widget_grab_focus();
366 case GDK_MOTION_NOTIFY:
367 event_x = ev->motion.x;
368 event_y = ev->motion.y;
369 group->w2i(event_x, event_y);
371 // convert event_x to global frame
372 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
373 trackview.editor().snap_to(event_frame);
374 // convert event_frame back to local coordinates relative to position
375 event_frame -= _region->position();
377 switch (_mouse_state) {
378 case Pressed: // Drag start
381 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
382 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
383 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
386 drag_start_x = event_x;
387 drag_start_y = event_y;
389 drag_rect = new ArdourCanvas::SimpleRect(*group);
390 drag_rect->property_x1() = event_x;
391 drag_rect->property_y1() = event_y;
392 drag_rect->property_x2() = event_x;
393 drag_rect->property_y2() = event_y;
394 drag_rect->property_outline_what() = 0xFF;
395 drag_rect->property_outline_color_rgba()
396 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
397 drag_rect->property_fill_color_rgba()
398 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
400 _mouse_state = SelectRectDragging;
403 // Add note drag start
404 } else if (editor.current_mouse_mode() == MouseRange) {
405 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
406 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
409 drag_start_x = event_x;
410 drag_start_y = event_y;
412 drag_rect = new ArdourCanvas::SimpleRect(*group);
413 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
415 drag_rect->property_y1() = midi_stream_view()->note_to_y(
416 midi_stream_view()->y_to_note(event_y));
417 drag_rect->property_x2() = event_x;
418 drag_rect->property_y2() = drag_rect->property_y1()
419 + floor(midi_stream_view()->note_height());
420 drag_rect->property_outline_what() = 0xFF;
421 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
422 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
424 _mouse_state = AddDragging;
430 case SelectRectDragging: // Select drag motion
431 case AddDragging: // Add note drag motion
432 if (ev->motion.is_hint) {
435 GdkModifierType state;
436 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
441 if (_mouse_state == AddDragging)
442 event_x = trackview.editor().frame_to_pixel(event_frame);
445 if (event_x > drag_start_x)
446 drag_rect->property_x2() = event_x;
448 drag_rect->property_x1() = event_x;
451 if (drag_rect && _mouse_state == SelectRectDragging) {
452 if (event_y > drag_start_y)
453 drag_rect->property_y2() = event_y;
455 drag_rect->property_y1() = event_y;
457 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
463 case SelectTouchDragging:
471 case GDK_BUTTON_RELEASE:
472 event_x = ev->motion.x;
473 event_y = ev->motion.y;
474 group->w2i(event_x, event_y);
475 group->ungrab(ev->button.time);
476 event_frame = trackview.editor().pixel_to_frame(event_x);
478 if (ev->button.button == 3) {
480 } else if (_pressed_button != 1) {
484 switch (_mouse_state) {
485 case Pressed: // Clicked
486 switch (editor.current_mouse_mode()) {
492 create_note_at(event_x, event_y, _default_note_length);
499 case SelectRectDragging: // Select drag done
504 case AddDragging: // Add drag done
506 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
507 const double x = drag_rect->property_x1();
508 const double length = trackview.editor().pixel_to_frame(
509 drag_rect->property_x2() - drag_rect->property_x1());
511 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
526 MidiRegionView::show_list_editor ()
529 _list_editor = new MidiListEditor (trackview.session(), midi_region());
531 _list_editor->present ();
534 /** Add a note to the model, and the view, at a canvas (click) coordinate.
535 * \param x horizontal position in pixels
536 * \param y vertical position in pixels
537 * \param length duration of the note in beats */
539 MidiRegionView::create_note_at(double x, double y, double length)
541 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
542 MidiStreamView* const view = mtv->midi_view();
544 double note = midi_stream_view()->y_to_note(y);
547 assert(note <= 127.0);
549 // Start of note in frames relative to region start
550 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
551 assert(start_frames >= 0);
554 length = frames_to_beats(
555 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
557 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
558 frames_to_beats(start_frames + _region->start()), length,
559 (uint8_t)note, 0x40));
561 view->update_note_range(new_note->note());
563 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
565 _model->apply_command(*trackview.session(), cmd);
567 play_midi_note (new_note);
571 MidiRegionView::clear_events()
576 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
577 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
582 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
587 _pgm_changes.clear();
589 _optimization_iterator = _events.end();
594 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
597 content_connection.disconnect ();
598 _model->ContentsChanged.connect (content_connection, boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
602 if (_enable_display) {
609 MidiRegionView::start_delta_command(string name)
611 if (!_delta_command) {
612 _delta_command = _model->new_delta_command(name);
617 MidiRegionView::start_diff_command(string name)
619 if (!_diff_command) {
620 _diff_command = _model->new_diff_command(name);
625 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
627 if (_delta_command) {
628 _delta_command->add(note);
631 _marked_for_selection.insert(note);
634 _marked_for_velocity.insert(note);
639 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
641 if (_delta_command && ev->note()) {
642 _delta_command->remove(ev->note());
647 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
648 MidiModel::DiffCommand::Property property,
652 _diff_command->change (ev->note(), property, val);
657 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
658 MidiModel::DiffCommand::Property property,
659 Evoral::MusicalTime val)
662 _diff_command->change (ev->note(), property, val);
667 MidiRegionView::apply_delta()
669 if (!_delta_command) {
673 // Mark all selected notes for selection when model reloads
674 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
675 _marked_for_selection.insert((*i)->note());
678 _model->apply_command(*trackview.session(), _delta_command);
680 midi_view()->midi_track()->diskstream()->playlist_modified();
682 _marked_for_selection.clear();
683 _marked_for_velocity.clear();
687 MidiRegionView::apply_diff ()
689 if (!_diff_command) {
693 _model->apply_command(*trackview.session(), _diff_command);
695 midi_view()->midi_track()->diskstream()->playlist_modified();
697 _marked_for_velocity.clear();
701 MidiRegionView::apply_delta_as_subcommand()
703 if (!_delta_command) {
707 // Mark all selected notes for selection when model reloads
708 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
709 _marked_for_selection.insert((*i)->note());
712 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
714 midi_view()->midi_track()->diskstream()->playlist_modified();
716 _marked_for_selection.clear();
717 _marked_for_velocity.clear();
721 MidiRegionView::apply_diff_as_subcommand()
723 if (!_diff_command) {
727 // Mark all selected notes for selection when model reloads
728 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
729 _marked_for_selection.insert((*i)->note());
732 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
734 midi_view()->midi_track()->diskstream()->playlist_modified();
736 _marked_for_selection.clear();
737 _marked_for_velocity.clear();
741 MidiRegionView::abort_command()
743 delete _delta_command;
745 delete _diff_command;
751 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
753 if (_optimization_iterator != _events.end()) {
754 ++_optimization_iterator;
757 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
758 return *_optimization_iterator;
761 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
762 if ((*_optimization_iterator)->note() == note) {
763 return *_optimization_iterator;
771 MidiRegionView::redisplay_model()
773 // Don't redisplay the model if we're currently recording and displaying that
779 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
783 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
787 MidiModel::ReadLock lock(_model->read_lock());
789 MidiModel::Notes& notes (_model->notes());
790 _optimization_iterator = _events.begin();
792 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
794 boost::shared_ptr<NoteType> note (*n);
795 CanvasNoteEvent* cne;
798 if (note_in_region_range (note, visible)) {
800 if ((cne = find_canvas_note (note)) != 0) {
807 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
809 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
821 add_note (note, visible);
826 if ((cne = find_canvas_note (note)) != 0) {
833 /* remove note items that are no longer valid */
835 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
836 if (!(*i)->valid ()) {
838 i = _events.erase (i);
845 display_program_changes();
847 _marked_for_selection.clear ();
848 _marked_for_velocity.clear ();
850 /* we may have caused _events to contain things out of order (e.g. if a note
851 moved earlier or later). we don't generally need them in time order, but
852 make a note that a sort is required for those cases that require it.
859 MidiRegionView::display_program_changes()
861 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
866 Glib::Mutex::Lock lock (control->list()->lock());
868 uint8_t channel = control->parameter().channel();
870 for (AutomationList::const_iterator event = control->list()->begin();
871 event != control->list()->end(); ++event) {
872 double event_time = (*event)->when;
873 double program_number = floor((*event)->value + 0.5);
875 // Get current value of bank select MSB at time of the program change
876 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
877 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
879 if (msb_control != 0) {
880 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
883 // Get current value of bank select LSB at time of the program change
884 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
885 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
887 if (lsb_control != 0) {
888 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
891 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
893 boost::shared_ptr<MIDI::Name::Patch> patch =
894 MIDI::Name::MidiPatchManager::instance().find_patch(
895 _model_name, _custom_device_mode, channel, patch_key);
897 PCEvent program_change(event_time, uint8_t(program_number), channel);
900 add_pgm_change(program_change, patch->name());
903 snprintf(buf, 4, "%d", int(program_number));
904 add_pgm_change(program_change, buf);
910 MidiRegionView::display_sysexes()
912 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
913 Evoral::MusicalTime time = (*i)->time();
918 for (uint32_t b = 0; b < (*i)->size(); ++b) {
919 str << int((*i)->buffer()[b]);
920 if (b != (*i)->size() -1) {
924 string text = str.str();
926 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
928 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
930 double height = midi_stream_view()->contents_height();
932 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
933 new CanvasSysEx(*this, *group, text, height, x, 1.0));
935 // Show unless program change is beyond the region bounds
936 if (time - _region->start() >= _region->length() || time < _region->start()) {
942 _sys_exes.push_back(sysex);
947 MidiRegionView::~MidiRegionView ()
949 in_destructor = true;
953 RegionViewGoingAway (this); /* EMIT_SIGNAL */
962 delete _delta_command;
966 MidiRegionView::region_resized (const PropertyChange& what_changed)
968 RegionView::region_resized(what_changed);
970 if (what_changed.contains (ARDOUR::Properties::position)) {
971 set_duration(_region->length(), 0);
972 if (_enable_display) {
979 MidiRegionView::reset_width_dependent_items (double pixel_width)
981 RegionView::reset_width_dependent_items(pixel_width);
982 assert(_pixel_width == pixel_width);
984 if (_enable_display) {
990 MidiRegionView::set_height (double height)
992 static const double FUDGE = 2.0;
993 const double old_height = _height;
994 RegionView::set_height(height);
995 _height = height - FUDGE;
997 apply_note_range(midi_stream_view()->lowest_note(),
998 midi_stream_view()->highest_note(),
999 height != old_height + FUDGE);
1002 name_pixbuf->raise_to_top();
1007 /** Apply the current note range from the stream view
1008 * by repositioning/hiding notes as necessary
1011 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1013 if (!_enable_display) {
1017 if (!force && _current_range_min == min && _current_range_max == max) {
1021 _current_range_min = min;
1022 _current_range_max = max;
1024 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1025 CanvasNoteEvent* event = *i;
1026 boost::shared_ptr<NoteType> note (event->note());
1028 if (note->note() < _current_range_min ||
1029 note->note() > _current_range_max) {
1035 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1037 const double y1 = midi_stream_view()->note_to_y(note->note());
1038 const double y2 = y1 + floor(midi_stream_view()->note_height());
1040 cnote->property_y1() = y1;
1041 cnote->property_y2() = y2;
1043 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1045 double x = trackview.editor().frame_to_pixel(
1046 beats_to_frames(note->time()) - _region->start());
1047 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1048 double y = midi_stream_view()->note_to_y(event->note()->note())
1049 + ((diamond_size-2.0) / 4.0);
1051 chit->set_height (diamond_size);
1052 chit->move (x - chit->x1(), y - chit->y1());
1059 MidiRegionView::add_ghost (TimeAxisView& tv)
1063 double unit_position = _region->position () / samples_per_unit;
1064 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1065 MidiGhostRegion* ghost;
1067 if (mtv && mtv->midi_view()) {
1068 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1069 to allow having midi notes on top of note lines and waveforms.
1071 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1073 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1076 ghost->set_height ();
1077 ghost->set_duration (_region->length() / samples_per_unit);
1078 ghosts.push_back (ghost);
1080 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1081 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1082 ghost->add_note(note);
1086 GhostRegion::CatchDeletion.connect (*this, ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1092 /** Begin tracking note state for successive calls to add_event
1095 MidiRegionView::begin_write()
1097 assert(!_active_notes);
1098 _active_notes = new CanvasNote*[128];
1099 for (unsigned i=0; i < 128; ++i) {
1100 _active_notes[i] = 0;
1105 /** Destroy note state for add_event
1108 MidiRegionView::end_write()
1110 delete[] _active_notes;
1112 _marked_for_selection.clear();
1113 _marked_for_velocity.clear();
1117 /** Resolve an active MIDI note (while recording).
1120 MidiRegionView::resolve_note(uint8_t note, double end_time)
1122 if (midi_view()->note_mode() != Sustained) {
1126 if (_active_notes && _active_notes[note]) {
1127 const nframes64_t end_time_frames = beats_to_frames(end_time);
1128 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1129 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1130 _active_notes[note] = 0;
1135 /** Extend active notes to rightmost edge of region (if length is changed)
1138 MidiRegionView::extend_active_notes()
1140 if (!_active_notes) {
1144 for (unsigned i=0; i < 128; ++i) {
1145 if (_active_notes[i]) {
1146 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1152 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1154 if (no_sound_notes || !trackview.editor().sound_notes()) {
1158 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1161 route_ui->midi_track()->write_immediate_event(
1162 note->on_event().size(), note->on_event().buffer());
1164 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1165 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1166 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1167 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1168 note_length_ms, G_PRIORITY_DEFAULT);
1172 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1174 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1177 route_ui->midi_track()->write_immediate_event(
1178 note->off_event().size(), note->off_event().buffer());
1184 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1186 const nframes64_t note_start_frames = beats_to_frames(note->time());
1188 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1189 (note_start_frames < _region->start());
1191 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1192 (note->note() <= midi_stream_view()->highest_note());
1198 MidiRegionView::update_note (CanvasNote* ev)
1200 boost::shared_ptr<NoteType> note = ev->note();
1202 const nframes64_t note_start_frames = beats_to_frames(note->time());
1203 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1205 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1206 const double y1 = midi_stream_view()->note_to_y(note->note());
1207 const double note_endpixel =
1208 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1210 ev->property_x1() = x;
1211 ev->property_y1() = y1;
1212 if (note->length() > 0) {
1213 ev->property_x2() = note_endpixel;
1215 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1217 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1219 if (note->length() == 0) {
1220 if (_active_notes) {
1221 assert(note->note() < 128);
1222 // If this note is already active there's a stuck note,
1223 // finish the old note rectangle
1224 if (_active_notes[note->note()]) {
1225 CanvasNote* const old_rect = _active_notes[note->note()];
1226 boost::shared_ptr<NoteType> old_note = old_rect->note();
1227 old_rect->property_x2() = x;
1228 old_rect->property_outline_what() = (guint32) 0xF;
1230 _active_notes[note->note()] = ev;
1232 /* outline all but right edge */
1233 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1235 /* outline all edges */
1236 ev->property_outline_what() = (guint32) 0xF;
1241 MidiRegionView::update_hit (CanvasHit* ev)
1243 boost::shared_ptr<NoteType> note = ev->note();
1245 const nframes64_t note_start_frames = beats_to_frames(note->time());
1246 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1247 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1248 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1253 /** Add a MIDI note to the view (with length).
1255 * If in sustained mode, notes with length 0 will be considered active
1256 * notes, and resolve_note should be called when the corresponding note off
1257 * event arrives, to properly display the note.
1260 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1262 CanvasNoteEvent* event = 0;
1264 assert(note->time() >= 0);
1265 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1267 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1269 if (midi_view()->note_mode() == Sustained) {
1271 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1273 update_note (ev_rect);
1277 MidiGhostRegion* gr;
1279 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1280 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1281 gr->add_note(ev_rect);
1285 } else if (midi_view()->note_mode() == Percussive) {
1287 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1289 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1291 update_hit (ev_diamond);
1300 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1301 note_selected(event, true);
1304 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1305 event->show_velocity();
1307 event->on_channel_selection_change(_last_channel_selection);
1308 _events.push_back(event);
1319 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1320 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1322 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1324 start_delta_command (_("step add"));
1325 delta_add_note (new_note, true, false);
1328 /* potentially extend region to hold new note */
1330 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1331 nframes64_t region_end = _region->position() + _region->length() - 1;
1333 if (end_frame > region_end) {
1334 _region->set_length (end_frame, this);
1341 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1343 assert(program.time >= 0);
1345 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1346 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1348 double height = midi_stream_view()->contents_height();
1350 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1351 new CanvasProgramChange(*this, *group,
1356 _custom_device_mode,
1357 program.time, program.channel, program.value));
1359 // Show unless program change is beyond the region bounds
1360 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1366 _pgm_changes.push_back(pgm_change);
1370 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1372 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1373 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1374 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1376 if (msb_control != 0) {
1377 msb = int(msb_control->get_float(true, time));
1378 cerr << "got msb " << msb;
1381 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1382 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1384 if (lsb_control != 0) {
1385 lsb = lsb_control->get_float(true, time);
1386 cerr << " got lsb " << lsb;
1389 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1390 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1391 float program_number = -1.0;
1392 if (program_control != 0) {
1393 program_number = program_control->get_float(true, time);
1394 cerr << " got program " << program_number << endl;
1397 key.msb = (int) floor(msb + 0.5);
1398 key.lsb = (int) floor(lsb + 0.5);
1399 key.program_number = (int) floor(program_number + 0.5);
1400 assert(key.is_sane());
1405 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1407 // TODO: Get the real event here and alter them at the original times
1408 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1409 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1410 if (msb_control != 0) {
1411 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1414 // TODO: Get the real event here and alter them at the original times
1415 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1416 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1417 if (lsb_control != 0) {
1418 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1421 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1422 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1424 assert(program_control != 0);
1425 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1431 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1433 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1434 alter_program_change(program_change_event, new_patch);
1438 MidiRegionView::previous_program(CanvasProgramChange& program)
1440 MIDI::Name::PatchPrimaryKey key;
1441 get_patch_key_at(program.event_time(), program.channel(), key);
1443 boost::shared_ptr<MIDI::Name::Patch> patch =
1444 MIDI::Name::MidiPatchManager::instance().previous_patch(
1446 _custom_device_mode,
1450 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1452 alter_program_change(program_change_event, patch->patch_primary_key());
1457 MidiRegionView::next_program(CanvasProgramChange& program)
1459 MIDI::Name::PatchPrimaryKey key;
1460 get_patch_key_at(program.event_time(), program.channel(), key);
1462 boost::shared_ptr<MIDI::Name::Patch> patch =
1463 MIDI::Name::MidiPatchManager::instance().next_patch(
1465 _custom_device_mode,
1469 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1471 alter_program_change(program_change_event, patch->patch_primary_key());
1476 MidiRegionView::delete_selection()
1478 if (_selection.empty()) {
1482 start_delta_command (_("delete selection"));
1484 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1485 if ((*i)->selected()) {
1486 _delta_command->remove((*i)->note());
1496 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1498 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1499 if ((*i)->selected() && (*i) != ev) {
1500 (*i)->selected(false);
1501 (*i)->hide_velocity();
1509 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1511 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1514 Selection::iterator tmp = i;
1517 (*i)->selected (false);
1518 _selection.erase (i);
1527 /* don't bother with removing this regionview from the editor selection,
1528 since we're about to add another note, and thus put/keep this
1529 regionview in the editor selection.
1532 if (!ev->selected()) {
1533 add_to_selection (ev);
1538 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1540 uint8_t low_note = 127;
1541 uint8_t high_note = 0;
1542 MidiModel::Notes& notes (_model->notes());
1543 _optimization_iterator = _events.begin();
1545 if (extend && _selection.empty()) {
1551 /* scan existing selection to get note range */
1553 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1554 if ((*i)->note()->note() < low_note) {
1555 low_note = (*i)->note()->note();
1557 if ((*i)->note()->note() > high_note) {
1558 high_note = (*i)->note()->note();
1562 low_note = min (low_note, notenum);
1563 high_note = max (high_note, notenum);
1566 no_sound_notes = true;
1568 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1570 boost::shared_ptr<NoteType> note (*n);
1571 CanvasNoteEvent* cne;
1572 bool select = false;
1574 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1576 if ((note->note() >= low_note && note->note() <= high_note)) {
1579 } else if (note->note() == notenum) {
1585 if ((cne = find_canvas_note (note)) != 0) {
1586 // extend is false because we've taken care of it,
1587 // since it extends by time range, not pitch.
1588 note_selected (cne, add, false);
1592 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1596 no_sound_notes = false;
1600 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1602 MidiModel::Notes& notes (_model->notes());
1603 _optimization_iterator = _events.begin();
1605 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1607 boost::shared_ptr<NoteType> note (*n);
1608 CanvasNoteEvent* cne;
1610 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1611 if ((cne = find_canvas_note (note)) != 0) {
1612 if (cne->selected()) {
1613 note_deselected (cne);
1615 note_selected (cne, true, false);
1623 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1626 clear_selection_except(ev);
1631 if (!ev->selected()) {
1632 add_to_selection (ev);
1636 /* find end of latest note selected, select all between that and the start of "ev" */
1638 Evoral::MusicalTime earliest = DBL_MAX;
1639 Evoral::MusicalTime latest = 0;
1641 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1642 if ((*i)->note()->end_time() > latest) {
1643 latest = (*i)->note()->end_time();
1645 if ((*i)->note()->time() < earliest) {
1646 earliest = (*i)->note()->time();
1650 if (ev->note()->end_time() > latest) {
1651 latest = ev->note()->end_time();
1654 if (ev->note()->time() < earliest) {
1655 earliest = ev->note()->time();
1658 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1660 /* find notes entirely within OR spanning the earliest..latest range */
1662 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1663 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1664 add_to_selection (*i);
1668 /* if events were guaranteed to be time sorted, we could do this.
1669 but as of sept 10th 2009, they no longer are.
1672 if ((*i)->note()->time() > latest) {
1681 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1683 remove_from_selection (ev);
1687 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1697 // TODO: Make this faster by storing the last updated selection rect, and only
1698 // adjusting things that are in the area that appears/disappeared.
1699 // We probably need a tree to be able to find events in O(log(n)) time.
1701 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1703 /* check if any corner of the note is inside the rect
1706 1) this is computing "touched by", not "contained by" the rect.
1707 2) this does not require that events be sorted in time.
1710 const double ix1 = (*i)->x1();
1711 const double ix2 = (*i)->x2();
1712 const double iy1 = (*i)->y1();
1713 const double iy2 = (*i)->y2();
1715 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1716 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1717 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1718 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1721 if (!(*i)->selected()) {
1722 add_to_selection (*i);
1724 } else if ((*i)->selected()) {
1725 // Not inside rectangle
1726 remove_from_selection (*i);
1732 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1734 Selection::iterator i = _selection.find (ev);
1736 if (i != _selection.end()) {
1737 _selection.erase (i);
1740 ev->selected (false);
1741 ev->hide_velocity ();
1743 if (_selection.empty()) {
1744 PublicEditor& editor (trackview.editor());
1745 editor.get_selection().remove (this);
1750 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1752 bool add_mrv_selection = false;
1754 if (_selection.empty()) {
1755 add_mrv_selection = true;
1758 if (_selection.insert (ev).second) {
1759 ev->selected (true);
1760 play_midi_note ((ev)->note());
1763 if (add_mrv_selection) {
1764 PublicEditor& editor (trackview.editor());
1765 editor.get_selection().add (this);
1770 MidiRegionView::move_selection(double dx, double dy)
1772 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1773 (*i)->move_event(dx, dy);
1778 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1780 assert (!_selection.empty());
1782 uint8_t lowest_note_in_selection = 127;
1783 uint8_t highest_note_in_selection = 0;
1784 uint8_t highest_note_difference = 0;
1786 // find highest and lowest notes first
1788 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1789 uint8_t pitch = (*i)->note()->note();
1790 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1791 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1795 cerr << "dnote: " << (int) dnote << endl;
1796 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1797 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1798 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1799 << int(highest_note_in_selection) << endl;
1800 cerr << "selection size: " << _selection.size() << endl;
1801 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1804 // Make sure the note pitch does not exceed the MIDI standard range
1805 if (highest_note_in_selection + dnote > 127) {
1806 highest_note_difference = highest_note_in_selection - 127;
1809 start_diff_command(_("move notes"));
1811 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1813 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1816 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1818 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1821 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1827 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1829 uint8_t original_pitch = (*i)->note()->note();
1830 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1832 // keep notes in standard midi range
1833 clamp_to_0_127(new_pitch);
1835 // keep original pitch if note is dragged outside valid midi range
1836 if ((original_pitch != 0 && new_pitch == 0)
1837 || (original_pitch != 127 && new_pitch == 127)) {
1838 new_pitch = original_pitch;
1841 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1842 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1844 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1849 // care about notes being moved beyond the upper/lower bounds on the canvas
1850 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1851 highest_note_in_selection > midi_stream_view()->highest_note()) {
1852 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1857 MidiRegionView::snap_pixel_to_frame(double x)
1859 PublicEditor& editor = trackview.editor();
1860 // x is region relative, convert it to global absolute frames
1861 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1862 editor.snap_to(frame);
1863 return frame - _region->position(); // convert back to region relative
1867 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1869 PublicEditor& editor = trackview.editor();
1870 // x is region relative, convert it to global absolute frames
1871 nframes64_t frame = x + _region->position();
1872 editor.snap_to(frame);
1873 return frame - _region->position(); // convert back to region relative
1877 MidiRegionView::snap_to_pixel(double x)
1879 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1883 MidiRegionView::get_position_pixels()
1885 nframes64_t region_frame = get_position();
1886 return trackview.editor().frame_to_pixel(region_frame);
1890 MidiRegionView::get_end_position_pixels()
1892 nframes64_t frame = get_position() + get_duration ();
1893 return trackview.editor().frame_to_pixel(frame);
1897 MidiRegionView::beats_to_frames(double beats) const
1899 return _time_converter.to(beats);
1903 MidiRegionView::frames_to_beats(nframes64_t frames) const
1905 return _time_converter.from(frames);
1909 MidiRegionView::begin_resizing (bool /*at_front*/)
1911 _resize_data.clear();
1913 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1914 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1916 // only insert CanvasNotes into the map
1918 NoteResizeData *resize_data = new NoteResizeData();
1919 resize_data->canvas_note = note;
1921 // create a new SimpleRect from the note which will be the resize preview
1922 SimpleRect *resize_rect = new SimpleRect(
1923 *group, note->x1(), note->y1(), note->x2(), note->y2());
1925 // calculate the colors: get the color settings
1926 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1927 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1930 // make the resize preview notes more transparent and bright
1931 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1933 // calculate color based on note velocity
1934 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1935 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1939 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1940 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1942 resize_data->resize_rect = resize_rect;
1943 _resize_data.push_back(resize_data);
1949 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1951 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1952 SimpleRect* resize_rect = (*i)->resize_rect;
1953 CanvasNote* canvas_note = (*i)->canvas_note;
1958 current_x = canvas_note->x1() + delta_x;
1960 // x is in track relative, transform it to region relative
1961 current_x = delta_x - get_position_pixels();
1965 current_x = canvas_note->x2() + delta_x;
1967 // x is in track relative, transform it to region relative
1968 current_x = delta_x - get_end_position_pixels ();
1973 resize_rect->property_x1() = snap_to_pixel(current_x);
1974 resize_rect->property_x2() = canvas_note->x2();
1976 resize_rect->property_x2() = snap_to_pixel(current_x);
1977 resize_rect->property_x1() = canvas_note->x1();
1983 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1985 start_diff_command(_("resize notes"));
1987 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1988 CanvasNote* canvas_note = (*i)->canvas_note;
1989 SimpleRect* resize_rect = (*i)->resize_rect;
1990 const double region_start = get_position_pixels();
1995 current_x = canvas_note->x1() + delta_x;
1997 // x is in track relative, transform it to region relative
1998 current_x = region_start + delta_x;
2002 current_x = canvas_note->x2() + delta_x;
2004 // x is in track relative, transform it to region relative
2005 current_x = region_start + delta_x;
2009 current_x = snap_pixel_to_frame (current_x);
2010 current_x = frames_to_beats (current_x);
2012 if (at_front && current_x < canvas_note->note()->end_time()) {
2013 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2015 double len = canvas_note->note()->time() - current_x;
2016 len += canvas_note->note()->length();
2019 /* XXX convert to beats */
2020 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2025 double len = current_x - canvas_note->note()->time();
2028 /* XXX convert to beats */
2029 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2037 _resize_data.clear();
2042 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2044 uint8_t new_velocity;
2047 new_velocity = event->note()->velocity() + velocity;
2048 clamp_to_0_127(new_velocity);
2050 new_velocity = velocity;
2053 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2057 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2062 new_note = event->note()->note() + note;
2067 clamp_to_0_127 (new_note);
2068 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2072 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2074 bool change_start = false;
2075 bool change_length = false;
2076 Evoral::MusicalTime new_start;
2077 Evoral::MusicalTime new_length;
2079 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2081 front_delta: if positive - move the start of the note later in time (shortening it)
2082 if negative - move the start of the note earlier in time (lengthening it)
2084 end_delta: if positive - move the end of the note later in time (lengthening it)
2085 if negative - move the end of the note earlier in time (shortening it)
2089 if (front_delta < 0) {
2091 if (event->note()->time() < -front_delta) {
2094 new_start = event->note()->time() + front_delta; // moves earlier
2097 /* start moved toward zero, so move the end point out to where it used to be.
2098 Note that front_delta is negative, so this increases the length.
2101 new_length = event->note()->length() - front_delta;
2102 change_start = true;
2103 change_length = true;
2107 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2109 if (new_pos < event->note()->end_time()) {
2110 new_start = event->note()->time() + front_delta;
2111 /* start moved toward the end, so move the end point back to where it used to be */
2112 new_length = event->note()->length() - front_delta;
2113 change_start = true;
2114 change_length = true;
2121 bool can_change = true;
2122 if (end_delta < 0) {
2123 if (event->note()->length() < -end_delta) {
2129 new_length = event->note()->length() + end_delta;
2130 change_length = true;
2135 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2138 if (change_length) {
2139 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2144 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2146 Evoral::MusicalTime new_time;
2150 if (event->note()->time() < -delta) {
2153 new_time = event->note()->time() + delta;
2156 new_time = event->note()->time() + delta;
2162 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2166 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2170 if (_selection.empty()) {
2185 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2186 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2192 start_diff_command(_("change velocities"));
2194 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2195 Selection::iterator next = i;
2197 change_note_velocity (*i, delta, true);
2206 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2208 if (_selection.empty()) {
2225 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2227 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2231 if ((int8_t) (*i)->note()->note() + delta > 127) {
2238 start_diff_command (_("transpose"));
2240 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2241 Selection::iterator next = i;
2243 change_note_note (*i, delta, true);
2251 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2253 Evoral::MusicalTime delta;
2258 /* grab the current grid distance */
2260 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2262 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2263 cerr << "Grid type not available as beats - TO BE FIXED\n";
2272 start_diff_command (_("change note lengths"));
2274 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2275 Selection::iterator next = i;
2278 /* note the negation of the delta for start */
2280 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2289 MidiRegionView::nudge_notes (bool forward)
2291 if (_selection.empty()) {
2295 /* pick a note as the point along the timeline to get the nudge distance.
2296 its not necessarily the earliest note, so we may want to pull the notes out
2297 into a vector and sort before using the first one.
2300 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2302 nframes64_t distance;
2304 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2306 /* grid is off - use nudge distance */
2308 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2314 nframes64_t next_pos = ref_point;
2317 /* XXX need check on max_frames, but that needs max_frames64 or something */
2320 if (next_pos == 0) {
2326 cerr << "ref point was " << ref_point << " next was " << next_pos;
2327 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2328 distance = ref_point - next_pos;
2329 cerr << " final is " << next_pos << " distance = " << distance << endl;
2332 if (distance == 0) {
2336 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2342 start_diff_command (_("nudge"));
2344 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2345 Selection::iterator next = i;
2347 change_note_time (*i, delta, true);
2355 MidiRegionView::change_channel(uint8_t channel)
2357 start_diff_command(_("change channel"));
2358 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2359 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2366 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2368 if (_mouse_state == SelectTouchDragging) {
2369 note_selected(ev, true);
2373 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2374 // This causes an infinite loop on note add sometimes
2375 //PublicEditor& editor (trackview.editor());
2376 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2377 //editor.show_verbose_canvas_cursor_with (buf);
2381 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2383 PublicEditor& editor (trackview.editor());
2384 editor.hide_verbose_canvas_cursor ();
2389 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2391 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2393 display_model(msrc->model());
2397 MidiRegionView::set_frame_color()
2400 if (_selected && should_show_selection) {
2401 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2403 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2409 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2413 case FilterChannels:
2414 _force_channel = -1;
2417 _force_channel = mask;
2418 mask = 0xFFFF; // Show all notes as active (below)
2421 // Update notes for selection
2422 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2423 (*i)->on_channel_selection_change(mask);
2426 _last_channel_selection = mask;
2430 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2432 _model_name = model;
2433 _custom_device_mode = custom_device_mode;
2438 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2440 if (_selection.empty()) {
2444 PublicEditor& editor (trackview.editor());
2449 editor.get_cut_buffer().add (selection_as_cut_buffer());
2455 start_delta_command();
2457 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2462 delta_remove_note (*i);
2473 MidiRegionView::selection_as_cut_buffer () const
2477 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2478 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2481 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2488 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2494 start_delta_command (_("paste"));
2496 Evoral::MusicalTime beat_delta;
2497 Evoral::MusicalTime paste_pos_beats;
2498 Evoral::MusicalTime duration;
2499 Evoral::MusicalTime end_point;
2501 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2502 paste_pos_beats = frames_to_beats (pos - _region->position());
2503 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2504 paste_pos_beats = 0;
2506 _selection.clear ();
2508 for (int n = 0; n < (int) times; ++n) {
2510 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2512 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2513 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2515 /* make all newly added notes selected */
2517 delta_add_note (copied_note, true);
2518 end_point = copied_note->end_time();
2521 paste_pos_beats += duration;
2524 /* if we pasted past the current end of the region, extend the region */
2526 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2527 nframes64_t region_end = _region->position() + _region->length() - 1;
2529 if (end_frame > region_end) {
2531 trackview.session()->begin_reversible_command (_("paste"));
2533 XMLNode& before (_region->get_state());
2534 _region->set_length (end_frame, this);
2535 trackview.session()->add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2541 struct EventNoteTimeEarlyFirstComparator {
2542 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2543 return a->note()->time() < b->note()->time();
2548 MidiRegionView::time_sort_events ()
2550 if (!_sort_needed) {
2554 EventNoteTimeEarlyFirstComparator cmp;
2557 _sort_needed = false;
2561 MidiRegionView::goto_next_note ()
2563 // nframes64_t pos = -1;
2564 bool use_next = false;
2566 if (_events.back()->selected()) {
2570 time_sort_events ();
2572 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2573 if ((*i)->selected()) {
2576 } else if (use_next) {
2578 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2583 /* use the first one */
2585 unique_select (_events.front());
2590 MidiRegionView::goto_previous_note ()
2592 // nframes64_t pos = -1;
2593 bool use_next = false;
2595 if (_events.front()->selected()) {
2599 time_sort_events ();
2601 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2602 if ((*i)->selected()) {
2605 } else if (use_next) {
2607 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2612 /* use the last one */
2614 unique_select (*(_events.rbegin()));
2618 MidiRegionView::selection_as_notelist (Notes& selected)
2620 time_sort_events ();
2622 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2623 if ((*i)->selected()) {
2624 selected.insert ((*i)->note());