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));
589 if (_enable_display) {
596 MidiRegionView::start_delta_command(string name)
598 if (!_delta_command) {
599 _delta_command = _model->new_delta_command(name);
604 MidiRegionView::start_diff_command(string name)
606 if (!_diff_command) {
607 _diff_command = _model->new_diff_command(name);
612 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
614 if (_delta_command) {
615 _delta_command->add(note);
618 _marked_for_selection.insert(note);
621 _marked_for_velocity.insert(note);
626 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
628 if (_delta_command && ev->note()) {
629 _delta_command->remove(ev->note());
634 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
635 MidiModel::DiffCommand::Property property,
639 _diff_command->change (ev->note(), property, val);
644 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
645 MidiModel::DiffCommand::Property property,
646 Evoral::MusicalTime val)
649 _diff_command->change (ev->note(), property, val);
654 MidiRegionView::apply_delta()
656 if (!_delta_command) {
660 // Mark all selected notes for selection when model reloads
661 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
662 _marked_for_selection.insert((*i)->note());
665 _model->apply_command(trackview.session(), _delta_command);
667 midi_view()->midi_track()->diskstream()->playlist_modified();
669 _marked_for_selection.clear();
670 _marked_for_velocity.clear();
674 MidiRegionView::apply_diff ()
676 if (!_diff_command) {
680 _model->apply_command(trackview.session(), _diff_command);
682 midi_view()->midi_track()->diskstream()->playlist_modified();
684 _marked_for_velocity.clear();
688 MidiRegionView::apply_delta_as_subcommand()
690 if (!_delta_command) {
694 // Mark all selected notes for selection when model reloads
695 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
696 _marked_for_selection.insert((*i)->note());
699 _model->apply_command_as_subcommand(trackview.session(), _delta_command);
701 midi_view()->midi_track()->diskstream()->playlist_modified();
703 _marked_for_selection.clear();
704 _marked_for_velocity.clear();
708 MidiRegionView::apply_diff_as_subcommand()
710 if (!_diff_command) {
714 // Mark all selected notes for selection when model reloads
715 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
716 _marked_for_selection.insert((*i)->note());
719 _model->apply_command_as_subcommand(trackview.session(), _diff_command);
721 midi_view()->midi_track()->diskstream()->playlist_modified();
723 _marked_for_selection.clear();
724 _marked_for_velocity.clear();
728 MidiRegionView::abort_command()
730 delete _delta_command;
732 delete _diff_command;
738 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
740 if (_optimization_iterator != _events.end()) {
741 ++_optimization_iterator;
744 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
745 return *_optimization_iterator;
748 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
749 if ((*_optimization_iterator)->note() == note) {
750 return *_optimization_iterator;
758 MidiRegionView::redisplay_model()
760 // Don't redisplay the model if we're currently recording and displaying that
766 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
770 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
776 MidiModel::Notes& notes (_model->notes());
777 _optimization_iterator = _events.begin();
779 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
781 boost::shared_ptr<NoteType> note (*n);
782 CanvasNoteEvent* cne;
785 if (note_in_region_range (note, visible)) {
787 if ((cne = find_canvas_note (note)) != 0) {
794 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
796 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
808 add_note (note, visible);
813 if ((cne = find_canvas_note (note)) != 0) {
820 /* remove note items that are no longer valid */
822 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
823 if (!(*i)->valid ()) {
825 i = _events.erase (i);
832 display_program_changes();
834 _model->read_unlock();
836 _marked_for_selection.clear ();
837 _marked_for_velocity.clear ();
839 /* we may have caused _events to contain things out of order (e.g. if a note
840 moved earlier or later). we don't generally need them in time order, but
841 make a note that a sort is required for those cases that require it.
848 MidiRegionView::display_program_changes()
850 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
855 Glib::Mutex::Lock lock (control->list()->lock());
857 uint8_t channel = control->parameter().channel();
859 for (AutomationList::const_iterator event = control->list()->begin();
860 event != control->list()->end(); ++event) {
861 double event_time = (*event)->when;
862 double program_number = floor((*event)->value + 0.5);
864 // Get current value of bank select MSB at time of the program change
865 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
866 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
868 if (msb_control != 0) {
869 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
872 // Get current value of bank select LSB at time of the program change
873 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
874 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
876 if (lsb_control != 0) {
877 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
880 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
882 boost::shared_ptr<MIDI::Name::Patch> patch =
883 MIDI::Name::MidiPatchManager::instance().find_patch(
884 _model_name, _custom_device_mode, channel, patch_key);
886 PCEvent program_change(event_time, uint8_t(program_number), channel);
889 add_pgm_change(program_change, patch->name());
892 snprintf(buf, 4, "%d", int(program_number));
893 add_pgm_change(program_change, buf);
899 MidiRegionView::display_sysexes()
901 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
902 Evoral::MusicalTime time = (*i)->time();
907 for (uint32_t b = 0; b < (*i)->size(); ++b) {
908 str << int((*i)->buffer()[b]);
909 if (b != (*i)->size() -1) {
913 string text = str.str();
915 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
917 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
919 double height = midi_stream_view()->contents_height();
921 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
922 new CanvasSysEx(*this, *group, text, height, x, 1.0));
924 // Show unless program change is beyond the region bounds
925 if (time - _region->start() >= _region->length() || time < _region->start()) {
931 _sys_exes.push_back(sysex);
936 MidiRegionView::~MidiRegionView ()
938 in_destructor = true;
940 RegionViewGoingAway (this); /* EMIT_SIGNAL */
949 delete _delta_command;
953 MidiRegionView::region_resized (Change what_changed)
955 RegionView::region_resized(what_changed);
957 if (what_changed & ARDOUR::PositionChanged) {
958 set_duration(_region->length(), 0);
959 if (_enable_display) {
966 MidiRegionView::reset_width_dependent_items (double pixel_width)
968 RegionView::reset_width_dependent_items(pixel_width);
969 assert(_pixel_width == pixel_width);
971 if (_enable_display) {
977 MidiRegionView::set_height (double height)
979 static const double FUDGE = 2.0;
980 const double old_height = _height;
981 RegionView::set_height(height);
982 _height = height - FUDGE;
984 apply_note_range(midi_stream_view()->lowest_note(),
985 midi_stream_view()->highest_note(),
986 height != old_height + FUDGE);
989 name_pixbuf->raise_to_top();
994 /** Apply the current note range from the stream view
995 * by repositioning/hiding notes as necessary
998 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1000 if (!_enable_display) {
1004 if (!force && _current_range_min == min && _current_range_max == max) {
1008 _current_range_min = min;
1009 _current_range_max = max;
1011 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1012 CanvasNoteEvent* event = *i;
1013 boost::shared_ptr<NoteType> note (event->note());
1015 if (note->note() < _current_range_min ||
1016 note->note() > _current_range_max) {
1022 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1024 const double y1 = midi_stream_view()->note_to_y(note->note());
1025 const double y2 = y1 + floor(midi_stream_view()->note_height());
1027 cnote->property_y1() = y1;
1028 cnote->property_y2() = y2;
1030 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1032 double x = trackview.editor().frame_to_pixel(
1033 beats_to_frames(note->time()) - _region->start());
1034 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1035 double y = midi_stream_view()->note_to_y(event->note()->note())
1036 + ((diamond_size-2.0) / 4.0);
1038 chit->set_height (diamond_size);
1039 chit->move (x - chit->x1(), y - chit->y1());
1046 MidiRegionView::add_ghost (TimeAxisView& tv)
1050 double unit_position = _region->position () / samples_per_unit;
1051 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1052 MidiGhostRegion* ghost;
1054 if (mtv && mtv->midi_view()) {
1055 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1056 to allow having midi notes on top of note lines and waveforms.
1058 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1060 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1063 ghost->set_height ();
1064 ghost->set_duration (_region->length() / samples_per_unit);
1065 ghosts.push_back (ghost);
1067 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1068 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1069 ghost->add_note(note);
1073 ghost->GoingAway.connect (mem_fun(*this, &MidiRegionView::remove_ghost));
1079 /** Begin tracking note state for successive calls to add_event
1082 MidiRegionView::begin_write()
1084 assert(!_active_notes);
1085 _active_notes = new CanvasNote*[128];
1086 for (unsigned i=0; i < 128; ++i) {
1087 _active_notes[i] = 0;
1092 /** Destroy note state for add_event
1095 MidiRegionView::end_write()
1097 delete[] _active_notes;
1099 _marked_for_selection.clear();
1100 _marked_for_velocity.clear();
1104 /** Resolve an active MIDI note (while recording).
1107 MidiRegionView::resolve_note(uint8_t note, double end_time)
1109 if (midi_view()->note_mode() != Sustained) {
1113 if (_active_notes && _active_notes[note]) {
1114 const nframes64_t end_time_frames = beats_to_frames(end_time);
1115 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1116 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1117 _active_notes[note] = 0;
1122 /** Extend active notes to rightmost edge of region (if length is changed)
1125 MidiRegionView::extend_active_notes()
1127 if (!_active_notes) {
1131 for (unsigned i=0; i < 128; ++i) {
1132 if (_active_notes[i]) {
1133 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1139 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1141 if (!trackview.editor().sound_notes()) {
1145 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1148 route_ui->midi_track()->write_immediate_event(
1149 note->on_event().size(), note->on_event().buffer());
1151 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1152 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1153 * (1000 / (double)route_ui->session().nominal_frame_rate());
1154 Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1155 note_length_ms, G_PRIORITY_DEFAULT);
1159 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1161 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1164 route_ui->midi_track()->write_immediate_event(
1165 note->off_event().size(), note->off_event().buffer());
1171 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1173 const nframes64_t note_start_frames = beats_to_frames(note->time());
1175 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1176 (note_start_frames < _region->start());
1178 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1179 (note->note() <= midi_stream_view()->highest_note());
1185 MidiRegionView::update_note (CanvasNote* ev)
1187 boost::shared_ptr<NoteType> note = ev->note();
1189 const nframes64_t note_start_frames = beats_to_frames(note->time());
1190 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1192 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1195 const double y1 = midi_stream_view()->note_to_y(note->note());
1196 const double note_endpixel =
1197 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1199 ev->property_x1() = x;
1200 ev->property_y1() = y1;
1201 if (note->length() > 0) {
1202 ev->property_x2() = note_endpixel;
1204 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1206 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1208 if (note->length() == 0) {
1209 if (_active_notes) {
1210 assert(note->note() < 128);
1211 // If this note is already active there's a stuck note,
1212 // finish the old note rectangle
1213 if (_active_notes[note->note()]) {
1214 CanvasNote* const old_rect = _active_notes[note->note()];
1215 boost::shared_ptr<NoteType> old_note = old_rect->note();
1216 old_rect->property_x2() = x;
1217 old_rect->property_outline_what() = (guint32) 0xF;
1219 _active_notes[note->note()] = ev;
1221 /* outline all but right edge */
1222 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1224 /* outline all edges */
1225 ev->property_outline_what() = (guint32) 0xF;
1230 MidiRegionView::update_hit (CanvasHit* ev)
1232 boost::shared_ptr<NoteType> note = ev->note();
1234 const nframes64_t note_start_frames = beats_to_frames(note->time());
1235 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1236 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1237 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1242 /** Add a MIDI note to the view (with length).
1244 * If in sustained mode, notes with length 0 will be considered active
1245 * notes, and resolve_note should be called when the corresponding note off
1246 * event arrives, to properly display the note.
1249 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1251 CanvasNoteEvent* event = 0;
1253 assert(note->time() >= 0);
1254 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1256 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1258 if (midi_view()->note_mode() == Sustained) {
1260 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1262 update_note (ev_rect);
1266 MidiGhostRegion* gr;
1268 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1269 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1270 gr->add_note(ev_rect);
1274 } else if (midi_view()->note_mode() == Percussive) {
1276 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1278 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1280 update_hit (ev_diamond);
1289 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1290 note_selected(event, true);
1293 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1294 event->show_velocity();
1296 event->on_channel_selection_change(_last_channel_selection);
1297 _events.push_back(event);
1308 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1309 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1311 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1313 start_delta_command (_("step add"));
1314 delta_add_note (new_note, true, false);
1317 /* potentially extend region to hold new note */
1319 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1320 nframes64_t region_end = _region->position() + _region->length() - 1;
1322 if (end_frame > region_end) {
1323 _region->set_length (end_frame, this);
1330 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1332 assert(program.time >= 0);
1334 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1335 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1337 double height = midi_stream_view()->contents_height();
1339 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1340 new CanvasProgramChange(*this, *group,
1345 _custom_device_mode,
1346 program.time, program.channel, program.value));
1348 // Show unless program change is beyond the region bounds
1349 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1355 _pgm_changes.push_back(pgm_change);
1359 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1361 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1362 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1363 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1365 if (msb_control != 0) {
1366 msb = int(msb_control->get_float(true, time));
1367 cerr << "got msb " << msb;
1370 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1371 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1373 if (lsb_control != 0) {
1374 lsb = lsb_control->get_float(true, time);
1375 cerr << " got lsb " << lsb;
1378 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1379 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1380 float program_number = -1.0;
1381 if (program_control != 0) {
1382 program_number = program_control->get_float(true, time);
1383 cerr << " got program " << program_number << endl;
1386 key.msb = (int) floor(msb + 0.5);
1387 key.lsb = (int) floor(lsb + 0.5);
1388 key.program_number = (int) floor(program_number + 0.5);
1389 assert(key.is_sane());
1394 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1396 // TODO: Get the real event here and alter them at the original times
1397 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1398 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1399 if (msb_control != 0) {
1400 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1403 // TODO: Get the real event here and alter them at the original times
1404 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1405 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1406 if (lsb_control != 0) {
1407 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1410 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1411 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1413 assert(program_control != 0);
1414 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1420 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1422 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1423 alter_program_change(program_change_event, new_patch);
1427 MidiRegionView::previous_program(CanvasProgramChange& program)
1429 MIDI::Name::PatchPrimaryKey key;
1430 get_patch_key_at(program.event_time(), program.channel(), key);
1432 boost::shared_ptr<MIDI::Name::Patch> patch =
1433 MIDI::Name::MidiPatchManager::instance().previous_patch(
1435 _custom_device_mode,
1439 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1441 alter_program_change(program_change_event, patch->patch_primary_key());
1446 MidiRegionView::next_program(CanvasProgramChange& program)
1448 MIDI::Name::PatchPrimaryKey key;
1449 get_patch_key_at(program.event_time(), program.channel(), key);
1451 boost::shared_ptr<MIDI::Name::Patch> patch =
1452 MIDI::Name::MidiPatchManager::instance().next_patch(
1454 _custom_device_mode,
1458 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1460 alter_program_change(program_change_event, patch->patch_primary_key());
1465 MidiRegionView::delete_selection()
1467 if (_selection.empty()) {
1471 start_delta_command (_("delete selection"));
1473 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1474 if ((*i)->selected()) {
1475 _delta_command->remove((*i)->note());
1485 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1487 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1488 if ((*i)->selected() && (*i) != ev) {
1489 (*i)->selected(false);
1490 (*i)->hide_velocity();
1498 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1500 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1502 Selection::iterator tmp = i;
1506 remove_from_selection (*i);
1512 if (!ev->selected()) {
1513 add_to_selection (ev);
1518 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1521 clear_selection_except(ev);
1526 if (!ev->selected()) {
1527 add_to_selection (ev);
1531 /* find end of latest note selected, select all between that and the start of "ev" */
1533 Evoral::MusicalTime earliest = DBL_MAX;
1534 Evoral::MusicalTime latest = 0;
1536 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1537 if ((*i)->note()->end_time() > latest) {
1538 latest = (*i)->note()->end_time();
1540 if ((*i)->note()->time() < earliest) {
1541 earliest = (*i)->note()->time();
1545 if (ev->note()->end_time() > latest) {
1546 latest = ev->note()->end_time();
1549 if (ev->note()->time() < earliest) {
1550 earliest = ev->note()->time();
1553 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1555 /* find notes entirely within OR spanning the earliest..latest range */
1557 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1558 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1559 add_to_selection (*i);
1563 /* if events were guaranteed to be time sorted, we could do this.
1564 but as of sept 10th 2009, they no longer are.
1567 if ((*i)->note()->time() > latest) {
1576 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1578 remove_from_selection (ev);
1582 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1592 // TODO: Make this faster by storing the last updated selection rect, and only
1593 // adjusting things that are in the area that appears/disappeared.
1594 // We probably need a tree to be able to find events in O(log(n)) time.
1596 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1598 /* check if any corner of the note is inside the rect
1601 1) this is computing "touched by", not "contained by" the rect.
1602 2) this does not require that events be sorted in time.
1605 const double ix1 = (*i)->x1();
1606 const double ix2 = (*i)->x2();
1607 const double iy1 = (*i)->y1();
1608 const double iy2 = (*i)->y2();
1610 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1611 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1612 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1613 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1616 if (!(*i)->selected()) {
1617 add_to_selection (*i);
1619 } else if ((*i)->selected()) {
1620 // Not inside rectangle
1621 remove_from_selection (*i);
1627 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1629 Selection::iterator i = _selection.find (ev);
1631 if (i != _selection.end()) {
1632 _selection.erase (i);
1635 ev->selected (false);
1636 ev->hide_velocity ();
1638 if (_selection.empty()) {
1639 PublicEditor& editor (trackview.editor());
1640 editor.get_selection().remove (this);
1645 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1647 bool add_mrv_selection = false;
1649 if (_selection.empty()) {
1650 add_mrv_selection = true;
1653 if (_selection.insert (ev).second) {
1654 ev->selected (true);
1655 play_midi_note ((ev)->note());
1658 if (add_mrv_selection) {
1659 PublicEditor& editor (trackview.editor());
1660 editor.get_selection().add (this);
1665 MidiRegionView::move_selection(double dx, double dy)
1667 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1668 (*i)->move_event(dx, dy);
1673 MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
1675 // TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy...
1676 if (_selection.find(ev) == _selection.end()) {
1680 uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note();
1681 uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
1682 uint8_t highest_note_difference = 0;
1684 // find highest and lowest notes first
1685 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1686 uint8_t pitch = (*i)->note()->note();
1687 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1688 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1692 cerr << "dnote: " << (int) dnote << endl;
1693 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1694 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1695 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1696 << int(highest_note_in_selection) << endl;
1697 cerr << "selection size: " << _selection.size() << endl;
1698 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1701 // Make sure the note pitch does not exceed the MIDI standard range
1702 if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
1703 highest_note_difference = highest_note_in_selection - 127;
1706 start_diff_command(_("move notes"));
1708 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1710 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1713 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1715 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1718 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1724 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1726 uint8_t original_pitch = (*i)->note()->note();
1727 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1729 // keep notes in standard midi range
1730 clamp_to_0_127(new_pitch);
1732 // keep original pitch if note is dragged outside valid midi range
1733 if ((original_pitch != 0 && new_pitch == 0)
1734 || (original_pitch != 127 && new_pitch == 127)) {
1735 new_pitch = original_pitch;
1738 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1739 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1741 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1746 // care about notes being moved beyond the upper/lower bounds on the canvas
1747 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1748 highest_note_in_selection > midi_stream_view()->highest_note()) {
1749 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1754 MidiRegionView::snap_pixel_to_frame(double x)
1756 PublicEditor& editor = trackview.editor();
1757 // x is region relative, convert it to global absolute frames
1758 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1759 editor.snap_to(frame);
1760 return frame - _region->position(); // convert back to region relative
1764 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1766 PublicEditor& editor = trackview.editor();
1767 // x is region relative, convert it to global absolute frames
1768 nframes64_t frame = x + _region->position();
1769 editor.snap_to(frame);
1770 return frame - _region->position(); // convert back to region relative
1774 MidiRegionView::snap_to_pixel(double x)
1776 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1780 MidiRegionView::get_position_pixels()
1782 nframes64_t region_frame = get_position();
1783 return trackview.editor().frame_to_pixel(region_frame);
1787 MidiRegionView::get_end_position_pixels()
1789 nframes64_t frame = get_position() + get_duration ();
1790 return trackview.editor().frame_to_pixel(frame);
1794 MidiRegionView::beats_to_frames(double beats) const
1796 return _time_converter.to(beats);
1800 MidiRegionView::frames_to_beats(nframes64_t frames) const
1802 return _time_converter.from(frames);
1806 MidiRegionView::begin_resizing(bool at_front)
1808 _resize_data.clear();
1810 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1811 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1813 // only insert CanvasNotes into the map
1815 NoteResizeData *resize_data = new NoteResizeData();
1816 resize_data->canvas_note = note;
1818 // create a new SimpleRect from the note which will be the resize preview
1819 SimpleRect *resize_rect = new SimpleRect(
1820 *group, note->x1(), note->y1(), note->x2(), note->y2());
1822 // calculate the colors: get the color settings
1823 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1824 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1827 // make the resize preview notes more transparent and bright
1828 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1830 // calculate color based on note velocity
1831 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1832 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1836 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1837 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1839 resize_data->resize_rect = resize_rect;
1840 _resize_data.push_back(resize_data);
1846 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1848 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1849 SimpleRect* resize_rect = (*i)->resize_rect;
1850 CanvasNote* canvas_note = (*i)->canvas_note;
1855 current_x = canvas_note->x1() + delta_x;
1857 // x is in track relative, transform it to region relative
1858 current_x = delta_x - get_position_pixels();
1862 current_x = canvas_note->x2() + delta_x;
1864 // x is in track relative, transform it to region relative
1865 current_x = delta_x - get_end_position_pixels ();
1870 resize_rect->property_x1() = snap_to_pixel(current_x);
1871 resize_rect->property_x2() = canvas_note->x2();
1873 resize_rect->property_x2() = snap_to_pixel(current_x);
1874 resize_rect->property_x1() = canvas_note->x1();
1880 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1882 start_diff_command(_("resize notes"));
1884 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1885 CanvasNote* canvas_note = (*i)->canvas_note;
1886 SimpleRect* resize_rect = (*i)->resize_rect;
1887 const double region_start = get_position_pixels();
1892 current_x = canvas_note->x1() + delta_x;
1894 // x is in track relative, transform it to region relative
1895 current_x = region_start + delta_x;
1899 current_x = canvas_note->x2() + delta_x;
1901 // x is in track relative, transform it to region relative
1902 current_x = region_start + delta_x;
1906 current_x = snap_pixel_to_frame (current_x);
1907 current_x = frames_to_beats (current_x);
1909 if (at_front && current_x < canvas_note->note()->end_time()) {
1910 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1914 double len = current_x - canvas_note->note()->time();
1917 /* XXX convert to beats */
1918 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1926 _resize_data.clear();
1931 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1933 uint8_t new_velocity;
1936 new_velocity = event->note()->velocity() + velocity;
1937 clamp_to_0_127(new_velocity);
1939 new_velocity = velocity;
1942 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1946 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1951 new_note = event->note()->note() + note;
1956 clamp_to_0_127 (new_note);
1957 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1961 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1963 bool change_start = false;
1964 bool change_length = false;
1965 Evoral::MusicalTime new_start;
1966 Evoral::MusicalTime new_length;
1968 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1970 front_delta: if positive - move the start of the note later in time (shortening it)
1971 if negative - move the start of the note earlier in time (lengthening it)
1973 end_delta: if positive - move the end of the note later in time (lengthening it)
1974 if negative - move the end of the note earlier in time (shortening it)
1978 if (front_delta < 0) {
1980 if (event->note()->time() < -front_delta) {
1983 new_start = event->note()->time() + front_delta; // moves earlier
1986 /* start moved toward zero, so move the end point out to where it used to be.
1987 Note that front_delta is negative, so this increases the length.
1990 new_length = event->note()->length() - front_delta;
1991 change_start = true;
1992 change_length = true;
1996 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
1998 if (new_pos < event->note()->end_time()) {
1999 new_start = event->note()->time() + front_delta;
2000 /* start moved toward the end, so move the end point back to where it used to be */
2001 new_length = event->note()->length() - front_delta;
2002 change_start = true;
2003 change_length = true;
2010 bool can_change = true;
2011 if (end_delta < 0) {
2012 if (event->note()->length() < -end_delta) {
2018 new_length = event->note()->length() + end_delta;
2019 change_length = true;
2024 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2027 if (change_length) {
2028 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2033 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2035 Evoral::MusicalTime new_time;
2039 if (event->note()->time() < -delta) {
2042 new_time = event->note()->time() + delta;
2045 new_time = event->note()->time() + delta;
2051 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2055 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2059 if (_selection.empty()) {
2074 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2075 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2081 start_diff_command(_("change velocities"));
2083 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2084 Selection::iterator next = i;
2086 change_note_velocity (*i, delta, true);
2095 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2097 if (_selection.empty()) {
2114 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2116 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2120 if ((int8_t) (*i)->note()->note() + delta > 127) {
2127 start_diff_command (_("transpose"));
2129 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2130 Selection::iterator next = i;
2132 change_note_note (*i, delta, true);
2140 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2142 Evoral::MusicalTime delta;
2147 /* grab the current grid distance */
2149 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2151 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2152 cerr << "Grid type not available as beats - TO BE FIXED\n";
2161 start_diff_command (_("change note lengths"));
2163 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2164 Selection::iterator next = i;
2167 /* note the negation of the delta for start */
2169 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2178 MidiRegionView::nudge_notes (bool forward)
2180 if (_selection.empty()) {
2184 /* pick a note as the point along the timeline to get the nudge distance.
2185 its not necessarily the earliest note, so we may want to pull the notes out
2186 into a vector and sort before using the first one.
2189 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2191 nframes64_t distance;
2193 if ((distance = trackview.editor().get_nudge_distance (ref_point, unused)) == 0) {
2195 /* no nudge distance set - use grid */
2197 nframes64_t next_pos = ref_point;
2200 /* XXX need check on max_frames, but that needs max_frames64 or something */
2203 if (next_pos == 0) {
2209 cerr << "ref point was " << ref_point << " next was " << next_pos;
2210 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2211 distance = ref_point - next_pos;
2212 cerr << " final is " << next_pos << " distance = " << distance << endl;
2215 if (distance == 0) {
2219 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2225 start_diff_command (_("nudge"));
2227 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2228 Selection::iterator next = i;
2230 change_note_time (*i, delta, true);
2238 MidiRegionView::change_channel(uint8_t channel)
2240 start_diff_command(_("change channel"));
2241 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2242 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2249 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2251 if (_mouse_state == SelectTouchDragging) {
2252 note_selected(ev, true);
2255 PublicEditor& editor (trackview.editor());
2256 editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2260 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* ev)
2262 PublicEditor& editor (trackview.editor());
2263 editor.hide_verbose_canvas_cursor ();
2268 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2270 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2272 display_model(msrc->model());
2276 MidiRegionView::set_frame_color()
2279 if (_selected && should_show_selection) {
2280 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2282 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2288 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2292 case FilterChannels:
2293 _force_channel = -1;
2296 _force_channel = mask;
2297 mask = 0xFFFF; // Show all notes as active (below)
2300 // Update notes for selection
2301 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2302 (*i)->on_channel_selection_change(mask);
2305 _last_channel_selection = mask;
2309 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2311 _model_name = model;
2312 _custom_device_mode = custom_device_mode;
2317 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2319 if (_selection.empty()) {
2323 PublicEditor& editor (trackview.editor());
2328 editor.get_cut_buffer().add (selection_as_cut_buffer());
2334 start_delta_command();
2336 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2341 delta_remove_note (*i);
2352 MidiRegionView::selection_as_cut_buffer () const
2356 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2357 notes.push_back (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2360 /* sort them into time order */
2362 Evoral::Sequence<Evoral::MusicalTime>::LaterNoteComparator cmp;
2363 sort (notes.begin(), notes.end(), cmp);
2365 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2372 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2378 start_delta_command (_("paste"));
2380 Evoral::MusicalTime beat_delta;
2381 Evoral::MusicalTime paste_pos_beats;
2382 Evoral::MusicalTime duration;
2383 Evoral::MusicalTime end_point;
2385 duration = mcb.notes().back()->end_time() - mcb.notes().front()->time();
2386 paste_pos_beats = frames_to_beats (pos - _region->position());
2387 beat_delta = mcb.notes().front()->time() - paste_pos_beats;
2388 paste_pos_beats = 0;
2390 _selection.clear ();
2392 for (int n = 0; n < (int) times; ++n) {
2394 for (NoteList::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2396 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2397 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2399 /* make all newly added notes selected */
2401 delta_add_note (copied_note, true);
2402 end_point = copied_note->end_time();
2405 paste_pos_beats += duration;
2408 /* if we pasted past the current end of the region, extend the region */
2410 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2411 nframes64_t region_end = _region->position() + _region->length() - 1;
2413 if (end_frame > region_end) {
2415 trackview.session().begin_reversible_command (_("paste"));
2417 XMLNode& before (_region->get_state());
2418 _region->set_length (end_frame, this);
2419 trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2425 struct EventNoteTimeEarlyFirstComparator {
2426 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2427 return a->note()->time() < b->note()->time();
2432 MidiRegionView::time_sort_events ()
2434 if (!_sort_needed) {
2438 EventNoteTimeEarlyFirstComparator cmp;
2441 _sort_needed = false;
2445 MidiRegionView::goto_next_note ()
2447 // nframes64_t pos = -1;
2448 bool use_next = false;
2450 if (_events.back()->selected()) {
2454 time_sort_events ();
2456 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2457 if ((*i)->selected()) {
2460 } else if (use_next) {
2462 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2467 /* use the first one */
2469 unique_select (_events.front());
2474 MidiRegionView::goto_previous_note ()
2476 // nframes64_t pos = -1;
2477 bool use_next = false;
2479 if (_events.front()->selected()) {
2483 time_sort_events ();
2485 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2486 if ((*i)->selected()) {
2489 } else if (use_next) {
2491 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2496 /* use the last one */
2498 unique_select (*(_events.rbegin()));
2502 MidiRegionView::selection_as_notelist (NoteList& selected)
2504 time_sort_events ();
2506 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2507 if ((*i)->selected()) {
2508 selected.push_back ((*i)->note());