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) {
324 } else if (ev->key.keyval == GDK_r) {
325 /* if we're not step editing, this really doesn't matter */
326 midi_view()->step_edit_rest ();
332 case GDK_KEY_RELEASE:
333 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
339 case GDK_BUTTON_PRESS:
340 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
341 _pressed_button = ev->button.button;
342 _mouse_state = Pressed;
345 _pressed_button = ev->button.button;
348 case GDK_2BUTTON_PRESS:
351 case GDK_ENTER_NOTIFY:
352 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
353 Keyboard::magic_widget_grab_focus();
357 case GDK_MOTION_NOTIFY:
358 event_x = ev->motion.x;
359 event_y = ev->motion.y;
360 group->w2i(event_x, event_y);
362 // convert event_x to global frame
363 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
364 trackview.editor().snap_to(event_frame);
365 // convert event_frame back to local coordinates relative to position
366 event_frame -= _region->position();
368 switch (_mouse_state) {
369 case Pressed: // Drag start
372 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
373 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
374 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
377 drag_start_x = event_x;
378 drag_start_y = event_y;
380 drag_rect = new ArdourCanvas::SimpleRect(*group);
381 drag_rect->property_x1() = event_x;
382 drag_rect->property_y1() = event_y;
383 drag_rect->property_x2() = event_x;
384 drag_rect->property_y2() = event_y;
385 drag_rect->property_outline_what() = 0xFF;
386 drag_rect->property_outline_color_rgba()
387 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
388 drag_rect->property_fill_color_rgba()
389 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
391 _mouse_state = SelectRectDragging;
394 // Add note drag start
395 } else if (editor.current_mouse_mode() == MouseRange) {
396 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
397 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
400 drag_start_x = event_x;
401 drag_start_y = event_y;
403 drag_rect = new ArdourCanvas::SimpleRect(*group);
404 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
406 drag_rect->property_y1() = midi_stream_view()->note_to_y(
407 midi_stream_view()->y_to_note(event_y));
408 drag_rect->property_x2() = event_x;
409 drag_rect->property_y2() = drag_rect->property_y1()
410 + floor(midi_stream_view()->note_height());
411 drag_rect->property_outline_what() = 0xFF;
412 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
413 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
415 _mouse_state = AddDragging;
421 case SelectRectDragging: // Select drag motion
422 case AddDragging: // Add note drag motion
423 if (ev->motion.is_hint) {
426 GdkModifierType state;
427 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
432 if (_mouse_state == AddDragging)
433 event_x = trackview.editor().frame_to_pixel(event_frame);
436 if (event_x > drag_start_x)
437 drag_rect->property_x2() = event_x;
439 drag_rect->property_x1() = event_x;
442 if (drag_rect && _mouse_state == SelectRectDragging) {
443 if (event_y > drag_start_y)
444 drag_rect->property_y2() = event_y;
446 drag_rect->property_y1() = event_y;
448 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
454 case SelectTouchDragging:
462 case GDK_BUTTON_RELEASE:
463 event_x = ev->motion.x;
464 event_y = ev->motion.y;
465 group->w2i(event_x, event_y);
466 group->ungrab(ev->button.time);
467 event_frame = trackview.editor().pixel_to_frame(event_x);
469 if (ev->button.button == 3) {
471 } else if (_pressed_button != 1) {
475 switch (_mouse_state) {
476 case Pressed: // Clicked
477 switch (editor.current_mouse_mode()) {
483 create_note_at(event_x, event_y, _default_note_length);
490 case SelectRectDragging: // Select drag done
495 case AddDragging: // Add drag done
497 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
498 const double x = drag_rect->property_x1();
499 const double length = trackview.editor().pixel_to_frame(
500 drag_rect->property_x2() - drag_rect->property_x1());
502 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
517 MidiRegionView::show_list_editor ()
519 MidiListEditor* mle = new MidiListEditor (trackview.session(), midi_region());
523 /** Add a note to the model, and the view, at a canvas (click) coordinate.
524 * \param x horizontal position in pixels
525 * \param y vertical position in pixels
526 * \param length duration of the note in beats */
528 MidiRegionView::create_note_at(double x, double y, double length)
530 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
531 MidiStreamView* const view = mtv->midi_view();
533 double note = midi_stream_view()->y_to_note(y);
536 assert(note <= 127.0);
538 // Start of note in frames relative to region start
539 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
540 assert(start_frames >= 0);
543 length = frames_to_beats(
544 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
546 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
547 frames_to_beats(start_frames + _region->start()), length,
548 (uint8_t)note, 0x40));
550 view->update_note_range(new_note->note());
552 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
554 _model->apply_command(trackview.session(), cmd);
556 play_midi_note (new_note);
560 MidiRegionView::clear_events()
565 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
566 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
571 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
576 _pgm_changes.clear();
578 _optimization_iterator = _events.end();
583 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
586 content_connection.disconnect ();
587 content_connection = _model->ContentsChanged.connect(sigc::mem_fun(this, &MidiRegionView::redisplay_model));
590 if (_enable_display) {
597 MidiRegionView::start_delta_command(string name)
599 if (!_delta_command) {
600 _delta_command = _model->new_delta_command(name);
605 MidiRegionView::start_diff_command(string name)
607 if (!_diff_command) {
608 _diff_command = _model->new_diff_command(name);
613 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
615 if (_delta_command) {
616 _delta_command->add(note);
619 _marked_for_selection.insert(note);
622 _marked_for_velocity.insert(note);
627 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
629 if (_delta_command && ev->note()) {
630 _delta_command->remove(ev->note());
635 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
636 MidiModel::DiffCommand::Property property,
640 _diff_command->change (ev->note(), property, val);
645 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
646 MidiModel::DiffCommand::Property property,
647 Evoral::MusicalTime val)
650 _diff_command->change (ev->note(), property, val);
655 MidiRegionView::apply_delta()
657 if (!_delta_command) {
661 // Mark all selected notes for selection when model reloads
662 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
663 _marked_for_selection.insert((*i)->note());
666 _model->apply_command(trackview.session(), _delta_command);
668 midi_view()->midi_track()->diskstream()->playlist_modified();
670 _marked_for_selection.clear();
671 _marked_for_velocity.clear();
675 MidiRegionView::apply_diff ()
677 if (!_diff_command) {
681 _model->apply_command(trackview.session(), _diff_command);
683 midi_view()->midi_track()->diskstream()->playlist_modified();
685 _marked_for_velocity.clear();
689 MidiRegionView::apply_delta_as_subcommand()
691 if (!_delta_command) {
695 // Mark all selected notes for selection when model reloads
696 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
697 _marked_for_selection.insert((*i)->note());
700 _model->apply_command_as_subcommand(trackview.session(), _delta_command);
702 midi_view()->midi_track()->diskstream()->playlist_modified();
704 _marked_for_selection.clear();
705 _marked_for_velocity.clear();
709 MidiRegionView::apply_diff_as_subcommand()
711 if (!_diff_command) {
715 // Mark all selected notes for selection when model reloads
716 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
717 _marked_for_selection.insert((*i)->note());
720 _model->apply_command_as_subcommand(trackview.session(), _diff_command);
722 midi_view()->midi_track()->diskstream()->playlist_modified();
724 _marked_for_selection.clear();
725 _marked_for_velocity.clear();
729 MidiRegionView::abort_command()
731 delete _delta_command;
733 delete _diff_command;
739 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
741 if (_optimization_iterator != _events.end()) {
742 ++_optimization_iterator;
745 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
746 return *_optimization_iterator;
749 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
750 if ((*_optimization_iterator)->note() == note) {
751 return *_optimization_iterator;
759 MidiRegionView::redisplay_model()
761 // Don't redisplay the model if we're currently recording and displaying that
767 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
771 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
775 MidiModel::ReadLock lock(_model->read_lock());
777 MidiModel::Notes& notes (_model->notes());
778 _optimization_iterator = _events.begin();
780 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
782 boost::shared_ptr<NoteType> note (*n);
783 CanvasNoteEvent* cne;
786 if (note_in_region_range (note, visible)) {
788 if ((cne = find_canvas_note (note)) != 0) {
795 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
797 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
809 add_note (note, visible);
814 if ((cne = find_canvas_note (note)) != 0) {
821 /* remove note items that are no longer valid */
823 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
824 if (!(*i)->valid ()) {
826 i = _events.erase (i);
833 display_program_changes();
835 _marked_for_selection.clear ();
836 _marked_for_velocity.clear ();
838 /* we may have caused _events to contain things out of order (e.g. if a note
839 moved earlier or later). we don't generally need them in time order, but
840 make a note that a sort is required for those cases that require it.
847 MidiRegionView::display_program_changes()
849 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
854 Glib::Mutex::Lock lock (control->list()->lock());
856 uint8_t channel = control->parameter().channel();
858 for (AutomationList::const_iterator event = control->list()->begin();
859 event != control->list()->end(); ++event) {
860 double event_time = (*event)->when;
861 double program_number = floor((*event)->value + 0.5);
863 // Get current value of bank select MSB at time of the program change
864 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
865 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
867 if (msb_control != 0) {
868 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
871 // Get current value of bank select LSB at time of the program change
872 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
873 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
875 if (lsb_control != 0) {
876 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
879 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
881 boost::shared_ptr<MIDI::Name::Patch> patch =
882 MIDI::Name::MidiPatchManager::instance().find_patch(
883 _model_name, _custom_device_mode, channel, patch_key);
885 PCEvent program_change(event_time, uint8_t(program_number), channel);
888 add_pgm_change(program_change, patch->name());
891 snprintf(buf, 4, "%d", int(program_number));
892 add_pgm_change(program_change, buf);
898 MidiRegionView::display_sysexes()
900 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
901 Evoral::MusicalTime time = (*i)->time();
906 for (uint32_t b = 0; b < (*i)->size(); ++b) {
907 str << int((*i)->buffer()[b]);
908 if (b != (*i)->size() -1) {
912 string text = str.str();
914 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
916 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
918 double height = midi_stream_view()->contents_height();
920 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
921 new CanvasSysEx(*this, *group, text, height, x, 1.0));
923 // Show unless program change is beyond the region bounds
924 if (time - _region->start() >= _region->length() || time < _region->start()) {
930 _sys_exes.push_back(sysex);
935 MidiRegionView::~MidiRegionView ()
937 in_destructor = true;
939 RegionViewGoingAway (this); /* EMIT_SIGNAL */
948 delete _delta_command;
952 MidiRegionView::region_resized (Change what_changed)
954 RegionView::region_resized(what_changed);
956 if (what_changed & ARDOUR::PositionChanged) {
957 set_duration(_region->length(), 0);
958 if (_enable_display) {
965 MidiRegionView::reset_width_dependent_items (double pixel_width)
967 RegionView::reset_width_dependent_items(pixel_width);
968 assert(_pixel_width == pixel_width);
970 if (_enable_display) {
976 MidiRegionView::set_height (double height)
978 static const double FUDGE = 2.0;
979 const double old_height = _height;
980 RegionView::set_height(height);
981 _height = height - FUDGE;
983 apply_note_range(midi_stream_view()->lowest_note(),
984 midi_stream_view()->highest_note(),
985 height != old_height + FUDGE);
988 name_pixbuf->raise_to_top();
993 /** Apply the current note range from the stream view
994 * by repositioning/hiding notes as necessary
997 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
999 if (!_enable_display) {
1003 if (!force && _current_range_min == min && _current_range_max == max) {
1007 _current_range_min = min;
1008 _current_range_max = max;
1010 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1011 CanvasNoteEvent* event = *i;
1012 boost::shared_ptr<NoteType> note (event->note());
1014 if (note->note() < _current_range_min ||
1015 note->note() > _current_range_max) {
1021 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1023 const double y1 = midi_stream_view()->note_to_y(note->note());
1024 const double y2 = y1 + floor(midi_stream_view()->note_height());
1026 cnote->property_y1() = y1;
1027 cnote->property_y2() = y2;
1029 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1031 double x = trackview.editor().frame_to_pixel(
1032 beats_to_frames(note->time()) - _region->start());
1033 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1034 double y = midi_stream_view()->note_to_y(event->note()->note())
1035 + ((diamond_size-2.0) / 4.0);
1037 chit->set_height (diamond_size);
1038 chit->move (x - chit->x1(), y - chit->y1());
1045 MidiRegionView::add_ghost (TimeAxisView& tv)
1049 double unit_position = _region->position () / samples_per_unit;
1050 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1051 MidiGhostRegion* ghost;
1053 if (mtv && mtv->midi_view()) {
1054 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1055 to allow having midi notes on top of note lines and waveforms.
1057 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1059 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1062 ghost->set_height ();
1063 ghost->set_duration (_region->length() / samples_per_unit);
1064 ghosts.push_back (ghost);
1066 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1067 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1068 ghost->add_note(note);
1072 ghost->GoingAway.connect (mem_fun(*this, &MidiRegionView::remove_ghost));
1078 /** Begin tracking note state for successive calls to add_event
1081 MidiRegionView::begin_write()
1083 assert(!_active_notes);
1084 _active_notes = new CanvasNote*[128];
1085 for (unsigned i=0; i < 128; ++i) {
1086 _active_notes[i] = 0;
1091 /** Destroy note state for add_event
1094 MidiRegionView::end_write()
1096 delete[] _active_notes;
1098 _marked_for_selection.clear();
1099 _marked_for_velocity.clear();
1103 /** Resolve an active MIDI note (while recording).
1106 MidiRegionView::resolve_note(uint8_t note, double end_time)
1108 if (midi_view()->note_mode() != Sustained) {
1112 if (_active_notes && _active_notes[note]) {
1113 const nframes64_t end_time_frames = beats_to_frames(end_time);
1114 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1115 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1116 _active_notes[note] = 0;
1121 /** Extend active notes to rightmost edge of region (if length is changed)
1124 MidiRegionView::extend_active_notes()
1126 if (!_active_notes) {
1130 for (unsigned i=0; i < 128; ++i) {
1131 if (_active_notes[i]) {
1132 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1138 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1140 if (!trackview.editor().sound_notes()) {
1144 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1147 route_ui->midi_track()->write_immediate_event(
1148 note->on_event().size(), note->on_event().buffer());
1150 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1151 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1152 * (1000 / (double)route_ui->session().nominal_frame_rate());
1153 Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1154 note_length_ms, G_PRIORITY_DEFAULT);
1158 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1160 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1163 route_ui->midi_track()->write_immediate_event(
1164 note->off_event().size(), note->off_event().buffer());
1170 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1172 const nframes64_t note_start_frames = beats_to_frames(note->time());
1174 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1175 (note_start_frames < _region->start());
1177 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1178 (note->note() <= midi_stream_view()->highest_note());
1184 MidiRegionView::update_note (CanvasNote* ev)
1186 boost::shared_ptr<NoteType> note = ev->note();
1188 const nframes64_t note_start_frames = beats_to_frames(note->time());
1189 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1191 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1192 const double y1 = midi_stream_view()->note_to_y(note->note());
1193 const double note_endpixel =
1194 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1196 ev->property_x1() = x;
1197 ev->property_y1() = y1;
1198 if (note->length() > 0) {
1199 ev->property_x2() = note_endpixel;
1201 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1203 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1205 if (note->length() == 0) {
1206 if (_active_notes) {
1207 assert(note->note() < 128);
1208 // If this note is already active there's a stuck note,
1209 // finish the old note rectangle
1210 if (_active_notes[note->note()]) {
1211 CanvasNote* const old_rect = _active_notes[note->note()];
1212 boost::shared_ptr<NoteType> old_note = old_rect->note();
1213 old_rect->property_x2() = x;
1214 old_rect->property_outline_what() = (guint32) 0xF;
1216 _active_notes[note->note()] = ev;
1218 /* outline all but right edge */
1219 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1221 /* outline all edges */
1222 ev->property_outline_what() = (guint32) 0xF;
1227 MidiRegionView::update_hit (CanvasHit* ev)
1229 boost::shared_ptr<NoteType> note = ev->note();
1231 const nframes64_t note_start_frames = beats_to_frames(note->time());
1232 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1233 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1234 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1239 /** Add a MIDI note to the view (with length).
1241 * If in sustained mode, notes with length 0 will be considered active
1242 * notes, and resolve_note should be called when the corresponding note off
1243 * event arrives, to properly display the note.
1246 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1248 CanvasNoteEvent* event = 0;
1250 assert(note->time() >= 0);
1251 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1253 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1255 if (midi_view()->note_mode() == Sustained) {
1257 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1259 update_note (ev_rect);
1263 MidiGhostRegion* gr;
1265 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1266 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1267 gr->add_note(ev_rect);
1271 } else if (midi_view()->note_mode() == Percussive) {
1273 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1275 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1277 update_hit (ev_diamond);
1286 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1287 note_selected(event, true);
1290 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1291 event->show_velocity();
1293 event->on_channel_selection_change(_last_channel_selection);
1294 _events.push_back(event);
1305 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1306 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1308 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1310 start_delta_command (_("step add"));
1311 delta_add_note (new_note, true, false);
1314 /* potentially extend region to hold new note */
1316 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1317 nframes64_t region_end = _region->position() + _region->length() - 1;
1319 if (end_frame > region_end) {
1320 _region->set_length (end_frame, this);
1327 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1329 assert(program.time >= 0);
1331 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1332 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1334 double height = midi_stream_view()->contents_height();
1336 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1337 new CanvasProgramChange(*this, *group,
1342 _custom_device_mode,
1343 program.time, program.channel, program.value));
1345 // Show unless program change is beyond the region bounds
1346 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1352 _pgm_changes.push_back(pgm_change);
1356 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1358 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1359 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1360 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1362 if (msb_control != 0) {
1363 msb = int(msb_control->get_float(true, time));
1364 cerr << "got msb " << msb;
1367 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1368 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1370 if (lsb_control != 0) {
1371 lsb = lsb_control->get_float(true, time);
1372 cerr << " got lsb " << lsb;
1375 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1376 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1377 float program_number = -1.0;
1378 if (program_control != 0) {
1379 program_number = program_control->get_float(true, time);
1380 cerr << " got program " << program_number << endl;
1383 key.msb = (int) floor(msb + 0.5);
1384 key.lsb = (int) floor(lsb + 0.5);
1385 key.program_number = (int) floor(program_number + 0.5);
1386 assert(key.is_sane());
1391 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1393 // TODO: Get the real event here and alter them at the original times
1394 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1395 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1396 if (msb_control != 0) {
1397 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1400 // TODO: Get the real event here and alter them at the original times
1401 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1402 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1403 if (lsb_control != 0) {
1404 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1407 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1408 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1410 assert(program_control != 0);
1411 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1417 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1419 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1420 alter_program_change(program_change_event, new_patch);
1424 MidiRegionView::previous_program(CanvasProgramChange& program)
1426 MIDI::Name::PatchPrimaryKey key;
1427 get_patch_key_at(program.event_time(), program.channel(), key);
1429 boost::shared_ptr<MIDI::Name::Patch> patch =
1430 MIDI::Name::MidiPatchManager::instance().previous_patch(
1432 _custom_device_mode,
1436 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1438 alter_program_change(program_change_event, patch->patch_primary_key());
1443 MidiRegionView::next_program(CanvasProgramChange& program)
1445 MIDI::Name::PatchPrimaryKey key;
1446 get_patch_key_at(program.event_time(), program.channel(), key);
1448 boost::shared_ptr<MIDI::Name::Patch> patch =
1449 MIDI::Name::MidiPatchManager::instance().next_patch(
1451 _custom_device_mode,
1455 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1457 alter_program_change(program_change_event, patch->patch_primary_key());
1462 MidiRegionView::delete_selection()
1464 if (_selection.empty()) {
1468 start_delta_command (_("delete selection"));
1470 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1471 if ((*i)->selected()) {
1472 _delta_command->remove((*i)->note());
1482 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1484 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1485 if ((*i)->selected() && (*i) != ev) {
1486 (*i)->selected(false);
1487 (*i)->hide_velocity();
1495 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1497 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1500 Selection::iterator tmp = i;
1503 (*i)->selected (false);
1504 _selection.erase (i);
1513 /* don't bother with removing this regionview from the editor selection,
1514 since we're about to add another note, and thus put/keep this
1515 regionview in the editor selection.
1518 if (!ev->selected()) {
1519 add_to_selection (ev);
1524 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1527 clear_selection_except(ev);
1532 if (!ev->selected()) {
1533 add_to_selection (ev);
1537 /* find end of latest note selected, select all between that and the start of "ev" */
1539 Evoral::MusicalTime earliest = DBL_MAX;
1540 Evoral::MusicalTime latest = 0;
1542 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1543 if ((*i)->note()->end_time() > latest) {
1544 latest = (*i)->note()->end_time();
1546 if ((*i)->note()->time() < earliest) {
1547 earliest = (*i)->note()->time();
1551 if (ev->note()->end_time() > latest) {
1552 latest = ev->note()->end_time();
1555 if (ev->note()->time() < earliest) {
1556 earliest = ev->note()->time();
1559 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1561 /* find notes entirely within OR spanning the earliest..latest range */
1563 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1564 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1565 add_to_selection (*i);
1569 /* if events were guaranteed to be time sorted, we could do this.
1570 but as of sept 10th 2009, they no longer are.
1573 if ((*i)->note()->time() > latest) {
1582 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1584 remove_from_selection (ev);
1588 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1598 // TODO: Make this faster by storing the last updated selection rect, and only
1599 // adjusting things that are in the area that appears/disappeared.
1600 // We probably need a tree to be able to find events in O(log(n)) time.
1602 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1604 /* check if any corner of the note is inside the rect
1607 1) this is computing "touched by", not "contained by" the rect.
1608 2) this does not require that events be sorted in time.
1611 const double ix1 = (*i)->x1();
1612 const double ix2 = (*i)->x2();
1613 const double iy1 = (*i)->y1();
1614 const double iy2 = (*i)->y2();
1616 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1617 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1618 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1619 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1622 if (!(*i)->selected()) {
1623 add_to_selection (*i);
1625 } else if ((*i)->selected()) {
1626 // Not inside rectangle
1627 remove_from_selection (*i);
1633 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1635 Selection::iterator i = _selection.find (ev);
1637 if (i != _selection.end()) {
1638 _selection.erase (i);
1641 ev->selected (false);
1642 ev->hide_velocity ();
1644 if (_selection.empty()) {
1645 PublicEditor& editor (trackview.editor());
1646 editor.get_selection().remove (this);
1651 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1653 bool add_mrv_selection = false;
1655 if (_selection.empty()) {
1656 add_mrv_selection = true;
1659 if (_selection.insert (ev).second) {
1660 ev->selected (true);
1661 play_midi_note ((ev)->note());
1664 if (add_mrv_selection) {
1665 PublicEditor& editor (trackview.editor());
1666 editor.get_selection().add (this);
1671 MidiRegionView::move_selection(double dx, double dy)
1673 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1674 (*i)->move_event(dx, dy);
1679 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1681 assert (!_selection.empty());
1683 uint8_t lowest_note_in_selection = 127;
1684 uint8_t highest_note_in_selection = 0;
1685 uint8_t highest_note_difference = 0;
1687 // find highest and lowest notes first
1689 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1690 uint8_t pitch = (*i)->note()->note();
1691 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1692 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1696 cerr << "dnote: " << (int) dnote << endl;
1697 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1698 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1699 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1700 << int(highest_note_in_selection) << endl;
1701 cerr << "selection size: " << _selection.size() << endl;
1702 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1705 // Make sure the note pitch does not exceed the MIDI standard range
1706 if (highest_note_in_selection + dnote > 127) {
1707 highest_note_difference = highest_note_in_selection - 127;
1710 start_diff_command(_("move notes"));
1712 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1714 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1717 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1719 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1722 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1728 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1730 uint8_t original_pitch = (*i)->note()->note();
1731 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1733 // keep notes in standard midi range
1734 clamp_to_0_127(new_pitch);
1736 // keep original pitch if note is dragged outside valid midi range
1737 if ((original_pitch != 0 && new_pitch == 0)
1738 || (original_pitch != 127 && new_pitch == 127)) {
1739 new_pitch = original_pitch;
1742 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1743 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1745 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1750 // care about notes being moved beyond the upper/lower bounds on the canvas
1751 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1752 highest_note_in_selection > midi_stream_view()->highest_note()) {
1753 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1758 MidiRegionView::snap_pixel_to_frame(double x)
1760 PublicEditor& editor = trackview.editor();
1761 // x is region relative, convert it to global absolute frames
1762 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1763 editor.snap_to(frame);
1764 return frame - _region->position(); // convert back to region relative
1768 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1770 PublicEditor& editor = trackview.editor();
1771 // x is region relative, convert it to global absolute frames
1772 nframes64_t frame = x + _region->position();
1773 editor.snap_to(frame);
1774 return frame - _region->position(); // convert back to region relative
1778 MidiRegionView::snap_to_pixel(double x)
1780 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1784 MidiRegionView::get_position_pixels()
1786 nframes64_t region_frame = get_position();
1787 return trackview.editor().frame_to_pixel(region_frame);
1791 MidiRegionView::get_end_position_pixels()
1793 nframes64_t frame = get_position() + get_duration ();
1794 return trackview.editor().frame_to_pixel(frame);
1798 MidiRegionView::beats_to_frames(double beats) const
1800 return _time_converter.to(beats);
1804 MidiRegionView::frames_to_beats(nframes64_t frames) const
1806 return _time_converter.from(frames);
1810 MidiRegionView::begin_resizing (bool /*at_front*/)
1812 _resize_data.clear();
1814 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1815 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1817 // only insert CanvasNotes into the map
1819 NoteResizeData *resize_data = new NoteResizeData();
1820 resize_data->canvas_note = note;
1822 // create a new SimpleRect from the note which will be the resize preview
1823 SimpleRect *resize_rect = new SimpleRect(
1824 *group, note->x1(), note->y1(), note->x2(), note->y2());
1826 // calculate the colors: get the color settings
1827 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1828 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1831 // make the resize preview notes more transparent and bright
1832 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1834 // calculate color based on note velocity
1835 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1836 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1840 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1841 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1843 resize_data->resize_rect = resize_rect;
1844 _resize_data.push_back(resize_data);
1850 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1852 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1853 SimpleRect* resize_rect = (*i)->resize_rect;
1854 CanvasNote* canvas_note = (*i)->canvas_note;
1859 current_x = canvas_note->x1() + delta_x;
1861 // x is in track relative, transform it to region relative
1862 current_x = delta_x - get_position_pixels();
1866 current_x = canvas_note->x2() + delta_x;
1868 // x is in track relative, transform it to region relative
1869 current_x = delta_x - get_end_position_pixels ();
1874 resize_rect->property_x1() = snap_to_pixel(current_x);
1875 resize_rect->property_x2() = canvas_note->x2();
1877 resize_rect->property_x2() = snap_to_pixel(current_x);
1878 resize_rect->property_x1() = canvas_note->x1();
1884 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1886 start_diff_command(_("resize notes"));
1888 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1889 CanvasNote* canvas_note = (*i)->canvas_note;
1890 SimpleRect* resize_rect = (*i)->resize_rect;
1891 const double region_start = get_position_pixels();
1896 current_x = canvas_note->x1() + delta_x;
1898 // x is in track relative, transform it to region relative
1899 current_x = region_start + delta_x;
1903 current_x = canvas_note->x2() + delta_x;
1905 // x is in track relative, transform it to region relative
1906 current_x = region_start + delta_x;
1910 current_x = snap_pixel_to_frame (current_x);
1911 current_x = frames_to_beats (current_x);
1913 if (at_front && current_x < canvas_note->note()->end_time()) {
1914 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1916 double len = canvas_note->note()->time() - current_x;
1917 len += canvas_note->note()->length();
1920 /* XXX convert to beats */
1921 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1926 double len = current_x - canvas_note->note()->time();
1929 /* XXX convert to beats */
1930 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1938 _resize_data.clear();
1943 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1945 uint8_t new_velocity;
1948 new_velocity = event->note()->velocity() + velocity;
1949 clamp_to_0_127(new_velocity);
1951 new_velocity = velocity;
1954 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1958 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1963 new_note = event->note()->note() + note;
1968 clamp_to_0_127 (new_note);
1969 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1973 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1975 bool change_start = false;
1976 bool change_length = false;
1977 Evoral::MusicalTime new_start;
1978 Evoral::MusicalTime new_length;
1980 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1982 front_delta: if positive - move the start of the note later in time (shortening it)
1983 if negative - move the start of the note earlier in time (lengthening it)
1985 end_delta: if positive - move the end of the note later in time (lengthening it)
1986 if negative - move the end of the note earlier in time (shortening it)
1990 if (front_delta < 0) {
1992 if (event->note()->time() < -front_delta) {
1995 new_start = event->note()->time() + front_delta; // moves earlier
1998 /* start moved toward zero, so move the end point out to where it used to be.
1999 Note that front_delta is negative, so this increases the length.
2002 new_length = event->note()->length() - front_delta;
2003 change_start = true;
2004 change_length = true;
2008 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2010 if (new_pos < event->note()->end_time()) {
2011 new_start = event->note()->time() + front_delta;
2012 /* start moved toward the end, so move the end point back to where it used to be */
2013 new_length = event->note()->length() - front_delta;
2014 change_start = true;
2015 change_length = true;
2022 bool can_change = true;
2023 if (end_delta < 0) {
2024 if (event->note()->length() < -end_delta) {
2030 new_length = event->note()->length() + end_delta;
2031 change_length = true;
2036 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2039 if (change_length) {
2040 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2045 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2047 Evoral::MusicalTime new_time;
2051 if (event->note()->time() < -delta) {
2054 new_time = event->note()->time() + delta;
2057 new_time = event->note()->time() + delta;
2063 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2067 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2071 if (_selection.empty()) {
2086 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2087 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2093 start_diff_command(_("change velocities"));
2095 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2096 Selection::iterator next = i;
2098 change_note_velocity (*i, delta, true);
2107 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2109 if (_selection.empty()) {
2126 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2128 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2132 if ((int8_t) (*i)->note()->note() + delta > 127) {
2139 start_diff_command (_("transpose"));
2141 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2142 Selection::iterator next = i;
2144 change_note_note (*i, delta, true);
2152 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2154 Evoral::MusicalTime delta;
2159 /* grab the current grid distance */
2161 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2163 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2164 cerr << "Grid type not available as beats - TO BE FIXED\n";
2173 start_diff_command (_("change note lengths"));
2175 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2176 Selection::iterator next = i;
2179 /* note the negation of the delta for start */
2181 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2190 MidiRegionView::nudge_notes (bool forward)
2192 if (_selection.empty()) {
2196 /* pick a note as the point along the timeline to get the nudge distance.
2197 its not necessarily the earliest note, so we may want to pull the notes out
2198 into a vector and sort before using the first one.
2201 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2203 nframes64_t distance;
2205 if ((distance = trackview.editor().get_nudge_distance (ref_point, unused)) == 0) {
2207 /* no nudge distance set - use grid */
2209 nframes64_t next_pos = ref_point;
2212 /* XXX need check on max_frames, but that needs max_frames64 or something */
2215 if (next_pos == 0) {
2221 cerr << "ref point was " << ref_point << " next was " << next_pos;
2222 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2223 distance = ref_point - next_pos;
2224 cerr << " final is " << next_pos << " distance = " << distance << endl;
2227 if (distance == 0) {
2231 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2237 start_diff_command (_("nudge"));
2239 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2240 Selection::iterator next = i;
2242 change_note_time (*i, delta, true);
2250 MidiRegionView::change_channel(uint8_t channel)
2252 start_diff_command(_("change channel"));
2253 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2254 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2261 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2263 if (_mouse_state == SelectTouchDragging) {
2264 note_selected(ev, true);
2267 PublicEditor& editor (trackview.editor());
2269 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2270 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2271 editor.show_verbose_canvas_cursor_with (buf);
2275 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2277 PublicEditor& editor (trackview.editor());
2278 editor.hide_verbose_canvas_cursor ();
2283 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2285 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2287 display_model(msrc->model());
2291 MidiRegionView::set_frame_color()
2294 if (_selected && should_show_selection) {
2295 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2297 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2303 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2307 case FilterChannels:
2308 _force_channel = -1;
2311 _force_channel = mask;
2312 mask = 0xFFFF; // Show all notes as active (below)
2315 // Update notes for selection
2316 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2317 (*i)->on_channel_selection_change(mask);
2320 _last_channel_selection = mask;
2324 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2326 _model_name = model;
2327 _custom_device_mode = custom_device_mode;
2332 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2334 if (_selection.empty()) {
2338 PublicEditor& editor (trackview.editor());
2343 editor.get_cut_buffer().add (selection_as_cut_buffer());
2349 start_delta_command();
2351 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2356 delta_remove_note (*i);
2367 MidiRegionView::selection_as_cut_buffer () const
2371 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2372 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2375 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2382 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2388 start_delta_command (_("paste"));
2390 Evoral::MusicalTime beat_delta;
2391 Evoral::MusicalTime paste_pos_beats;
2392 Evoral::MusicalTime duration;
2393 Evoral::MusicalTime end_point;
2395 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2396 paste_pos_beats = frames_to_beats (pos - _region->position());
2397 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2398 paste_pos_beats = 0;
2400 _selection.clear ();
2402 for (int n = 0; n < (int) times; ++n) {
2404 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2406 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2407 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2409 /* make all newly added notes selected */
2411 delta_add_note (copied_note, true);
2412 end_point = copied_note->end_time();
2415 paste_pos_beats += duration;
2418 /* if we pasted past the current end of the region, extend the region */
2420 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2421 nframes64_t region_end = _region->position() + _region->length() - 1;
2423 if (end_frame > region_end) {
2425 trackview.session().begin_reversible_command (_("paste"));
2427 XMLNode& before (_region->get_state());
2428 _region->set_length (end_frame, this);
2429 trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2435 struct EventNoteTimeEarlyFirstComparator {
2436 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2437 return a->note()->time() < b->note()->time();
2442 MidiRegionView::time_sort_events ()
2444 if (!_sort_needed) {
2448 EventNoteTimeEarlyFirstComparator cmp;
2451 _sort_needed = false;
2455 MidiRegionView::goto_next_note ()
2457 // nframes64_t pos = -1;
2458 bool use_next = false;
2460 if (_events.back()->selected()) {
2464 time_sort_events ();
2466 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2467 if ((*i)->selected()) {
2470 } else if (use_next) {
2472 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2477 /* use the first one */
2479 unique_select (_events.front());
2484 MidiRegionView::goto_previous_note ()
2486 // nframes64_t pos = -1;
2487 bool use_next = false;
2489 if (_events.front()->selected()) {
2493 time_sort_events ();
2495 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2496 if ((*i)->selected()) {
2499 } else if (use_next) {
2501 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2506 /* use the last one */
2508 unique_select (*(_events.rbegin()));
2512 MidiRegionView::selection_as_notelist (Notes& selected)
2514 time_sort_events ();
2516 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2517 if ((*i)->selected()) {
2518 selected.insert ((*i)->note());