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(
588 sigc::mem_fun(this, &MidiRegionView::redisplay_model));
591 if (_enable_display) {
598 MidiRegionView::start_delta_command(string name)
600 if (!_delta_command) {
601 _delta_command = _model->new_delta_command(name);
606 MidiRegionView::start_diff_command(string name)
608 if (!_diff_command) {
609 _diff_command = _model->new_diff_command(name);
614 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
616 if (_delta_command) {
617 _delta_command->add(note);
620 _marked_for_selection.insert(note);
623 _marked_for_velocity.insert(note);
628 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
630 if (_delta_command && ev->note()) {
631 _delta_command->remove(ev->note());
636 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
637 MidiModel::DiffCommand::Property property,
641 _diff_command->change (ev->note(), property, val);
646 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
647 MidiModel::DiffCommand::Property property,
648 Evoral::MusicalTime val)
651 _diff_command->change (ev->note(), property, val);
656 MidiRegionView::apply_delta()
658 if (!_delta_command) {
662 // Mark all selected notes for selection when model reloads
663 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
664 _marked_for_selection.insert((*i)->note());
667 _model->apply_command(trackview.session(), _delta_command);
669 midi_view()->midi_track()->diskstream()->playlist_modified();
671 _marked_for_selection.clear();
672 _marked_for_velocity.clear();
676 MidiRegionView::apply_diff ()
678 if (!_diff_command) {
682 _model->apply_command(trackview.session(), _diff_command);
684 midi_view()->midi_track()->diskstream()->playlist_modified();
686 _marked_for_velocity.clear();
690 MidiRegionView::apply_delta_as_subcommand()
692 if (!_delta_command) {
696 // Mark all selected notes for selection when model reloads
697 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
698 _marked_for_selection.insert((*i)->note());
701 _model->apply_command_as_subcommand(trackview.session(), _delta_command);
703 midi_view()->midi_track()->diskstream()->playlist_modified();
705 _marked_for_selection.clear();
706 _marked_for_velocity.clear();
710 MidiRegionView::apply_diff_as_subcommand()
712 if (!_diff_command) {
716 // Mark all selected notes for selection when model reloads
717 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
718 _marked_for_selection.insert((*i)->note());
721 _model->apply_command_as_subcommand(trackview.session(), _diff_command);
723 midi_view()->midi_track()->diskstream()->playlist_modified();
725 _marked_for_selection.clear();
726 _marked_for_velocity.clear();
730 MidiRegionView::abort_command()
732 delete _delta_command;
734 delete _diff_command;
740 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
742 if (_optimization_iterator != _events.end()) {
743 ++_optimization_iterator;
746 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
747 return *_optimization_iterator;
750 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
751 if ((*_optimization_iterator)->note() == note) {
752 return *_optimization_iterator;
760 MidiRegionView::redisplay_model()
762 // Don't redisplay the model if we're currently recording and displaying that
768 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
772 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
776 MidiModel::ReadLock lock(_model->read_lock());
778 MidiModel::Notes& notes (_model->notes());
779 _optimization_iterator = _events.begin();
781 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
783 boost::shared_ptr<NoteType> note (*n);
784 CanvasNoteEvent* cne;
787 if (note_in_region_range (note, visible)) {
789 if ((cne = find_canvas_note (note)) != 0) {
796 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
798 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
810 add_note (note, visible);
815 if ((cne = find_canvas_note (note)) != 0) {
822 /* remove note items that are no longer valid */
824 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
825 if (!(*i)->valid ()) {
827 i = _events.erase (i);
834 display_program_changes();
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());
1193 const double y1 = midi_stream_view()->note_to_y(note->note());
1194 const double note_endpixel =
1195 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1197 ev->property_x1() = x;
1198 ev->property_y1() = y1;
1199 if (note->length() > 0) {
1200 ev->property_x2() = note_endpixel;
1202 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1204 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1206 if (note->length() == 0) {
1207 if (_active_notes) {
1208 assert(note->note() < 128);
1209 // If this note is already active there's a stuck note,
1210 // finish the old note rectangle
1211 if (_active_notes[note->note()]) {
1212 CanvasNote* const old_rect = _active_notes[note->note()];
1213 boost::shared_ptr<NoteType> old_note = old_rect->note();
1214 old_rect->property_x2() = x;
1215 old_rect->property_outline_what() = (guint32) 0xF;
1217 _active_notes[note->note()] = ev;
1219 /* outline all but right edge */
1220 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1222 /* outline all edges */
1223 ev->property_outline_what() = (guint32) 0xF;
1228 MidiRegionView::update_hit (CanvasHit* ev)
1230 boost::shared_ptr<NoteType> note = ev->note();
1232 const nframes64_t note_start_frames = beats_to_frames(note->time());
1233 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1234 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1235 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1240 /** Add a MIDI note to the view (with length).
1242 * If in sustained mode, notes with length 0 will be considered active
1243 * notes, and resolve_note should be called when the corresponding note off
1244 * event arrives, to properly display the note.
1247 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1249 CanvasNoteEvent* event = 0;
1251 assert(note->time() >= 0);
1252 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1254 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1256 if (midi_view()->note_mode() == Sustained) {
1258 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1260 update_note (ev_rect);
1264 MidiGhostRegion* gr;
1266 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1267 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1268 gr->add_note(ev_rect);
1272 } else if (midi_view()->note_mode() == Percussive) {
1274 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1276 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1278 update_hit (ev_diamond);
1287 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1288 note_selected(event, true);
1291 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1292 event->show_velocity();
1294 event->on_channel_selection_change(_last_channel_selection);
1295 _events.push_back(event);
1306 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1307 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1309 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1311 start_delta_command (_("step add"));
1312 delta_add_note (new_note, true, false);
1315 /* potentially extend region to hold new note */
1317 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1318 nframes64_t region_end = _region->position() + _region->length() - 1;
1320 if (end_frame > region_end) {
1321 _region->set_length (end_frame, this);
1328 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1330 assert(program.time >= 0);
1332 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1333 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1335 double height = midi_stream_view()->contents_height();
1337 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1338 new CanvasProgramChange(*this, *group,
1343 _custom_device_mode,
1344 program.time, program.channel, program.value));
1346 // Show unless program change is beyond the region bounds
1347 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1353 _pgm_changes.push_back(pgm_change);
1357 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1359 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1360 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1361 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1363 if (msb_control != 0) {
1364 msb = int(msb_control->get_float(true, time));
1365 cerr << "got msb " << msb;
1368 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1369 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1371 if (lsb_control != 0) {
1372 lsb = lsb_control->get_float(true, time);
1373 cerr << " got lsb " << lsb;
1376 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1377 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1378 float program_number = -1.0;
1379 if (program_control != 0) {
1380 program_number = program_control->get_float(true, time);
1381 cerr << " got program " << program_number << endl;
1384 key.msb = (int) floor(msb + 0.5);
1385 key.lsb = (int) floor(lsb + 0.5);
1386 key.program_number = (int) floor(program_number + 0.5);
1387 assert(key.is_sane());
1392 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1394 // TODO: Get the real event here and alter them at the original times
1395 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1396 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1397 if (msb_control != 0) {
1398 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1401 // TODO: Get the real event here and alter them at the original times
1402 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1403 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1404 if (lsb_control != 0) {
1405 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1408 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1409 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1411 assert(program_control != 0);
1412 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1418 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1420 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1421 alter_program_change(program_change_event, new_patch);
1425 MidiRegionView::previous_program(CanvasProgramChange& program)
1427 MIDI::Name::PatchPrimaryKey key;
1428 get_patch_key_at(program.event_time(), program.channel(), key);
1430 boost::shared_ptr<MIDI::Name::Patch> patch =
1431 MIDI::Name::MidiPatchManager::instance().previous_patch(
1433 _custom_device_mode,
1437 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1439 alter_program_change(program_change_event, patch->patch_primary_key());
1444 MidiRegionView::next_program(CanvasProgramChange& program)
1446 MIDI::Name::PatchPrimaryKey key;
1447 get_patch_key_at(program.event_time(), program.channel(), key);
1449 boost::shared_ptr<MIDI::Name::Patch> patch =
1450 MIDI::Name::MidiPatchManager::instance().next_patch(
1452 _custom_device_mode,
1456 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1458 alter_program_change(program_change_event, patch->patch_primary_key());
1463 MidiRegionView::delete_selection()
1465 if (_selection.empty()) {
1469 start_delta_command (_("delete selection"));
1471 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1472 if ((*i)->selected()) {
1473 _delta_command->remove((*i)->note());
1483 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1485 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1486 if ((*i)->selected() && (*i) != ev) {
1487 (*i)->selected(false);
1488 (*i)->hide_velocity();
1496 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1498 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1501 Selection::iterator tmp = i;
1504 (*i)->selected (false);
1505 _selection.erase (i);
1514 /* don't bother with removing this regionview from the editor selection,
1515 since we're about to add another note, and thus put/keep this
1516 regionview in the editor selection.
1519 if (!ev->selected()) {
1520 add_to_selection (ev);
1525 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1528 clear_selection_except(ev);
1533 if (!ev->selected()) {
1534 add_to_selection (ev);
1538 /* find end of latest note selected, select all between that and the start of "ev" */
1540 Evoral::MusicalTime earliest = DBL_MAX;
1541 Evoral::MusicalTime latest = 0;
1543 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1544 if ((*i)->note()->end_time() > latest) {
1545 latest = (*i)->note()->end_time();
1547 if ((*i)->note()->time() < earliest) {
1548 earliest = (*i)->note()->time();
1552 if (ev->note()->end_time() > latest) {
1553 latest = ev->note()->end_time();
1556 if (ev->note()->time() < earliest) {
1557 earliest = ev->note()->time();
1560 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1562 /* find notes entirely within OR spanning the earliest..latest range */
1564 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1565 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1566 add_to_selection (*i);
1570 /* if events were guaranteed to be time sorted, we could do this.
1571 but as of sept 10th 2009, they no longer are.
1574 if ((*i)->note()->time() > latest) {
1583 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1585 remove_from_selection (ev);
1589 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1599 // TODO: Make this faster by storing the last updated selection rect, and only
1600 // adjusting things that are in the area that appears/disappeared.
1601 // We probably need a tree to be able to find events in O(log(n)) time.
1603 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1605 /* check if any corner of the note is inside the rect
1608 1) this is computing "touched by", not "contained by" the rect.
1609 2) this does not require that events be sorted in time.
1612 const double ix1 = (*i)->x1();
1613 const double ix2 = (*i)->x2();
1614 const double iy1 = (*i)->y1();
1615 const double iy2 = (*i)->y2();
1617 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1618 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1619 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1620 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1623 if (!(*i)->selected()) {
1624 add_to_selection (*i);
1626 } else if ((*i)->selected()) {
1627 // Not inside rectangle
1628 remove_from_selection (*i);
1634 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1636 Selection::iterator i = _selection.find (ev);
1638 if (i != _selection.end()) {
1639 _selection.erase (i);
1642 ev->selected (false);
1643 ev->hide_velocity ();
1645 if (_selection.empty()) {
1646 PublicEditor& editor (trackview.editor());
1647 editor.get_selection().remove (this);
1652 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1654 bool add_mrv_selection = false;
1656 if (_selection.empty()) {
1657 add_mrv_selection = true;
1660 if (_selection.insert (ev).second) {
1661 ev->selected (true);
1662 play_midi_note ((ev)->note());
1665 if (add_mrv_selection) {
1666 PublicEditor& editor (trackview.editor());
1667 editor.get_selection().add (this);
1672 MidiRegionView::move_selection(double dx, double dy)
1674 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1675 (*i)->move_event(dx, dy);
1680 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1682 assert (!_selection.empty());
1684 uint8_t lowest_note_in_selection = 127;
1685 uint8_t highest_note_in_selection = 0;
1686 uint8_t highest_note_difference = 0;
1688 // find highest and lowest notes first
1690 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1691 uint8_t pitch = (*i)->note()->note();
1692 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1693 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1697 cerr << "dnote: " << (int) dnote << endl;
1698 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1699 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1700 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1701 << int(highest_note_in_selection) << endl;
1702 cerr << "selection size: " << _selection.size() << endl;
1703 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1706 // Make sure the note pitch does not exceed the MIDI standard range
1707 if (highest_note_in_selection + dnote > 127) {
1708 highest_note_difference = highest_note_in_selection - 127;
1711 start_diff_command(_("move notes"));
1713 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1715 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1718 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1720 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1723 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1729 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1731 uint8_t original_pitch = (*i)->note()->note();
1732 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1734 // keep notes in standard midi range
1735 clamp_to_0_127(new_pitch);
1737 // keep original pitch if note is dragged outside valid midi range
1738 if ((original_pitch != 0 && new_pitch == 0)
1739 || (original_pitch != 127 && new_pitch == 127)) {
1740 new_pitch = original_pitch;
1743 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1744 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1746 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1751 // care about notes being moved beyond the upper/lower bounds on the canvas
1752 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1753 highest_note_in_selection > midi_stream_view()->highest_note()) {
1754 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1759 MidiRegionView::snap_pixel_to_frame(double x)
1761 PublicEditor& editor = trackview.editor();
1762 // x is region relative, convert it to global absolute frames
1763 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1764 editor.snap_to(frame);
1765 return frame - _region->position(); // convert back to region relative
1769 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1771 PublicEditor& editor = trackview.editor();
1772 // x is region relative, convert it to global absolute frames
1773 nframes64_t frame = x + _region->position();
1774 editor.snap_to(frame);
1775 return frame - _region->position(); // convert back to region relative
1779 MidiRegionView::snap_to_pixel(double x)
1781 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1785 MidiRegionView::get_position_pixels()
1787 nframes64_t region_frame = get_position();
1788 return trackview.editor().frame_to_pixel(region_frame);
1792 MidiRegionView::get_end_position_pixels()
1794 nframes64_t frame = get_position() + get_duration ();
1795 return trackview.editor().frame_to_pixel(frame);
1799 MidiRegionView::beats_to_frames(double beats) const
1801 return _time_converter.to(beats);
1805 MidiRegionView::frames_to_beats(nframes64_t frames) const
1807 return _time_converter.from(frames);
1811 MidiRegionView::begin_resizing (bool /*at_front*/)
1813 _resize_data.clear();
1815 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1816 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1818 // only insert CanvasNotes into the map
1820 NoteResizeData *resize_data = new NoteResizeData();
1821 resize_data->canvas_note = note;
1823 // create a new SimpleRect from the note which will be the resize preview
1824 SimpleRect *resize_rect = new SimpleRect(
1825 *group, note->x1(), note->y1(), note->x2(), note->y2());
1827 // calculate the colors: get the color settings
1828 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1829 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1832 // make the resize preview notes more transparent and bright
1833 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1835 // calculate color based on note velocity
1836 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1837 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1841 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1842 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1844 resize_data->resize_rect = resize_rect;
1845 _resize_data.push_back(resize_data);
1851 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1853 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1854 SimpleRect* resize_rect = (*i)->resize_rect;
1855 CanvasNote* canvas_note = (*i)->canvas_note;
1860 current_x = canvas_note->x1() + delta_x;
1862 // x is in track relative, transform it to region relative
1863 current_x = delta_x - get_position_pixels();
1867 current_x = canvas_note->x2() + delta_x;
1869 // x is in track relative, transform it to region relative
1870 current_x = delta_x - get_end_position_pixels ();
1875 resize_rect->property_x1() = snap_to_pixel(current_x);
1876 resize_rect->property_x2() = canvas_note->x2();
1878 resize_rect->property_x2() = snap_to_pixel(current_x);
1879 resize_rect->property_x1() = canvas_note->x1();
1885 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1887 start_diff_command(_("resize notes"));
1889 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1890 CanvasNote* canvas_note = (*i)->canvas_note;
1891 SimpleRect* resize_rect = (*i)->resize_rect;
1892 const double region_start = get_position_pixels();
1897 current_x = canvas_note->x1() + delta_x;
1899 // x is in track relative, transform it to region relative
1900 current_x = region_start + delta_x;
1904 current_x = canvas_note->x2() + delta_x;
1906 // x is in track relative, transform it to region relative
1907 current_x = region_start + delta_x;
1911 current_x = snap_pixel_to_frame (current_x);
1912 current_x = frames_to_beats (current_x);
1914 if (at_front && current_x < canvas_note->note()->end_time()) {
1915 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1917 double len = canvas_note->note()->time() - current_x;
1918 len += canvas_note->note()->length();
1921 /* XXX convert to beats */
1922 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1927 double len = current_x - canvas_note->note()->time();
1930 /* XXX convert to beats */
1931 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1939 _resize_data.clear();
1944 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1946 uint8_t new_velocity;
1949 new_velocity = event->note()->velocity() + velocity;
1950 clamp_to_0_127(new_velocity);
1952 new_velocity = velocity;
1955 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1959 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1964 new_note = event->note()->note() + note;
1969 clamp_to_0_127 (new_note);
1970 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1974 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1976 bool change_start = false;
1977 bool change_length = false;
1978 Evoral::MusicalTime new_start;
1979 Evoral::MusicalTime new_length;
1981 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1983 front_delta: if positive - move the start of the note later in time (shortening it)
1984 if negative - move the start of the note earlier in time (lengthening it)
1986 end_delta: if positive - move the end of the note later in time (lengthening it)
1987 if negative - move the end of the note earlier in time (shortening it)
1991 if (front_delta < 0) {
1993 if (event->note()->time() < -front_delta) {
1996 new_start = event->note()->time() + front_delta; // moves earlier
1999 /* start moved toward zero, so move the end point out to where it used to be.
2000 Note that front_delta is negative, so this increases the length.
2003 new_length = event->note()->length() - front_delta;
2004 change_start = true;
2005 change_length = true;
2009 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2011 if (new_pos < event->note()->end_time()) {
2012 new_start = event->note()->time() + front_delta;
2013 /* start moved toward the end, so move the end point back to where it used to be */
2014 new_length = event->note()->length() - front_delta;
2015 change_start = true;
2016 change_length = true;
2023 bool can_change = true;
2024 if (end_delta < 0) {
2025 if (event->note()->length() < -end_delta) {
2031 new_length = event->note()->length() + end_delta;
2032 change_length = true;
2037 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2040 if (change_length) {
2041 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2046 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2048 Evoral::MusicalTime new_time;
2052 if (event->note()->time() < -delta) {
2055 new_time = event->note()->time() + delta;
2058 new_time = event->note()->time() + delta;
2064 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2068 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2072 if (_selection.empty()) {
2087 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2088 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2094 start_diff_command(_("change velocities"));
2096 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2097 Selection::iterator next = i;
2099 change_note_velocity (*i, delta, true);
2108 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2110 if (_selection.empty()) {
2127 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2129 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2133 if ((int8_t) (*i)->note()->note() + delta > 127) {
2140 start_diff_command (_("transpose"));
2142 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2143 Selection::iterator next = i;
2145 change_note_note (*i, delta, true);
2153 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2155 Evoral::MusicalTime delta;
2160 /* grab the current grid distance */
2162 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2164 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2165 cerr << "Grid type not available as beats - TO BE FIXED\n";
2174 start_diff_command (_("change note lengths"));
2176 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2177 Selection::iterator next = i;
2180 /* note the negation of the delta for start */
2182 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2191 MidiRegionView::nudge_notes (bool forward)
2193 if (_selection.empty()) {
2197 /* pick a note as the point along the timeline to get the nudge distance.
2198 its not necessarily the earliest note, so we may want to pull the notes out
2199 into a vector and sort before using the first one.
2202 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2204 nframes64_t distance;
2206 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2208 /* grid is off - use nudge distance */
2210 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2216 nframes64_t next_pos = ref_point;
2219 /* XXX need check on max_frames, but that needs max_frames64 or something */
2222 if (next_pos == 0) {
2228 cerr << "ref point was " << ref_point << " next was " << next_pos;
2229 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2230 distance = ref_point - next_pos;
2231 cerr << " final is " << next_pos << " distance = " << distance << endl;
2234 if (distance == 0) {
2238 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2244 start_diff_command (_("nudge"));
2246 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2247 Selection::iterator next = i;
2249 change_note_time (*i, delta, true);
2257 MidiRegionView::change_channel(uint8_t channel)
2259 start_diff_command(_("change channel"));
2260 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2261 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2268 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2270 if (_mouse_state == SelectTouchDragging) {
2271 note_selected(ev, true);
2275 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2276 // This causes an infinite loop on note add sometimes
2277 //PublicEditor& editor (trackview.editor());
2278 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2279 //editor.show_verbose_canvas_cursor_with (buf);
2283 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2285 PublicEditor& editor (trackview.editor());
2286 editor.hide_verbose_canvas_cursor ();
2291 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2293 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2295 display_model(msrc->model());
2299 MidiRegionView::set_frame_color()
2302 if (_selected && should_show_selection) {
2303 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2305 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2311 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2315 case FilterChannels:
2316 _force_channel = -1;
2319 _force_channel = mask;
2320 mask = 0xFFFF; // Show all notes as active (below)
2323 // Update notes for selection
2324 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2325 (*i)->on_channel_selection_change(mask);
2328 _last_channel_selection = mask;
2332 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2334 _model_name = model;
2335 _custom_device_mode = custom_device_mode;
2340 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2342 if (_selection.empty()) {
2346 PublicEditor& editor (trackview.editor());
2351 editor.get_cut_buffer().add (selection_as_cut_buffer());
2357 start_delta_command();
2359 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2364 delta_remove_note (*i);
2375 MidiRegionView::selection_as_cut_buffer () const
2379 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2380 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2383 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2390 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2396 start_delta_command (_("paste"));
2398 Evoral::MusicalTime beat_delta;
2399 Evoral::MusicalTime paste_pos_beats;
2400 Evoral::MusicalTime duration;
2401 Evoral::MusicalTime end_point;
2403 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2404 paste_pos_beats = frames_to_beats (pos - _region->position());
2405 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2406 paste_pos_beats = 0;
2408 _selection.clear ();
2410 for (int n = 0; n < (int) times; ++n) {
2412 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2414 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2415 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2417 /* make all newly added notes selected */
2419 delta_add_note (copied_note, true);
2420 end_point = copied_note->end_time();
2423 paste_pos_beats += duration;
2426 /* if we pasted past the current end of the region, extend the region */
2428 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2429 nframes64_t region_end = _region->position() + _region->length() - 1;
2431 if (end_frame > region_end) {
2433 trackview.session().begin_reversible_command (_("paste"));
2435 XMLNode& before (_region->get_state());
2436 _region->set_length (end_frame, this);
2437 trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2443 struct EventNoteTimeEarlyFirstComparator {
2444 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2445 return a->note()->time() < b->note()->time();
2450 MidiRegionView::time_sort_events ()
2452 if (!_sort_needed) {
2456 EventNoteTimeEarlyFirstComparator cmp;
2459 _sort_needed = false;
2463 MidiRegionView::goto_next_note ()
2465 // nframes64_t pos = -1;
2466 bool use_next = false;
2468 if (_events.back()->selected()) {
2472 time_sort_events ();
2474 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2475 if ((*i)->selected()) {
2478 } else if (use_next) {
2480 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2485 /* use the first one */
2487 unique_select (_events.front());
2492 MidiRegionView::goto_previous_note ()
2494 // nframes64_t pos = -1;
2495 bool use_next = false;
2497 if (_events.front()->selected()) {
2501 time_sort_events ();
2503 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2504 if ((*i)->selected()) {
2507 } else if (use_next) {
2509 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2514 /* use the last one */
2516 unique_select (*(_events.rbegin()));
2520 MidiRegionView::selection_as_notelist (Notes& selected)
2522 time_sort_events ();
2524 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2525 if ((*i)->selected()) {
2526 selected.insert ((*i)->note());