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"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/Control.hpp"
44 #include "evoral/midi_util.h"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas-program-change.h"
51 #include "ghostregion.h"
52 #include "gui_thread.h"
54 #include "midi_cut_buffer.h"
55 #include "midi_list_editor.h"
56 #include "midi_region_view.h"
57 #include "midi_streamview.h"
58 #include "midi_time_axis.h"
59 #include "midi_time_axis.h"
60 #include "midi_util.h"
61 #include "public_editor.h"
62 #include "selection.h"
63 #include "simpleline.h"
64 #include "streamview.h"
69 using namespace ARDOUR;
71 using namespace Editing;
72 using namespace ArdourCanvas;
73 using Gtkmm2ext::Keyboard;
75 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
76 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
77 : RegionView (parent, tv, r, spu, basic_color)
79 , _last_channel_selection(0xFFFF)
80 , _current_range_min(0)
81 , _current_range_max(0)
82 , _model_name(string())
83 , _custom_device_mode(string())
85 , _note_group(new ArdourCanvas::Group(*parent))
91 , _optimization_iterator (_events.end())
93 , no_sound_notes (false)
95 _note_group->raise_to_top();
98 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
99 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
100 TimeAxisViewItem::Visibility visibility)
101 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
103 , _last_channel_selection(0xFFFF)
104 , _model_name(string())
105 , _custom_device_mode(string())
107 , _note_group(new ArdourCanvas::Group(*parent))
112 , _sort_needed (true)
113 , _optimization_iterator (_events.end())
115 , no_sound_notes (false)
118 _note_group->raise_to_top();
122 MidiRegionView::MidiRegionView (const MidiRegionView& other)
123 : sigc::trackable(other)
126 , _last_channel_selection(0xFFFF)
127 , _model_name(string())
128 , _custom_device_mode(string())
130 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
135 , _sort_needed (true)
136 , _optimization_iterator (_events.end())
138 , no_sound_notes (false)
143 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
144 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
149 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
150 : RegionView (other, boost::shared_ptr<Region> (region))
152 , _last_channel_selection(0xFFFF)
153 , _model_name(string())
154 , _custom_device_mode(string())
156 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
161 , _sort_needed (true)
162 , _optimization_iterator (_events.end())
164 , no_sound_notes (false)
169 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
170 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
176 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
178 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
179 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
183 midi_region()->midi_source(0)->load_model();
186 _model = midi_region()->midi_source(0)->model();
187 _enable_display = false;
189 RegionView::init (basic_color, false);
191 compute_colors (basic_color);
193 set_height (trackview.current_height());
196 region_sync_changed ();
197 region_resized (ARDOUR::bounds_change);
200 reset_width_dependent_items (_pixel_width);
204 _enable_display = true;
207 display_model (_model);
211 group->raise_to_top();
212 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
214 midi_view()->signal_channel_mode_changed().connect(
215 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
217 midi_view()->signal_midi_patch_settings_changed().connect(
218 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
222 MidiRegionView::canvas_event(GdkEvent* ev)
224 PublicEditor& editor (trackview.editor());
226 if (!editor.internal_editing()) {
230 static double drag_start_x, drag_start_y;
231 static double last_x, last_y;
232 double event_x, event_y;
233 nframes64_t event_frame = 0;
236 static ArdourCanvas::SimpleRect* drag_rect = 0;
238 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
239 to its items, which means that ev->type == GDK_SCROLL will never be seen
244 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
246 if (ev->scroll.direction == GDK_SCROLL_UP) {
247 change_velocities (true, fine, false);
249 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
250 change_velocities (false, fine, false);
259 /* since GTK bindings are generally activated on press, and since
260 detectable auto-repeat is the name of the game and only sends
261 repeated presses, carry out key actions at key press, not release.
264 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
265 _mouse_state = SelectTouchDragging;
268 } else if (ev->key.keyval == GDK_Escape) {
272 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
274 bool start = (ev->key.keyval == GDK_comma);
275 bool end = (ev->key.keyval == GDK_period);
276 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
277 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
279 change_note_lengths (fine, shorter, start, end);
283 } else if (ev->key.keyval == GDK_Delete) {
288 } else if (ev->key.keyval == GDK_Tab) {
290 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
291 goto_previous_note ();
297 } else if (ev->key.keyval == GDK_Up) {
299 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
300 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
302 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
303 change_velocities (true, fine, allow_smush);
305 transpose (true, fine, allow_smush);
309 } else if (ev->key.keyval == GDK_Down) {
311 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
312 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
314 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
315 change_velocities (false, fine, allow_smush);
317 transpose (false, fine, allow_smush);
321 } else if (ev->key.keyval == GDK_Left) {
326 } else if (ev->key.keyval == GDK_Right) {
331 } else if (ev->key.keyval == GDK_Control_L) {
334 } else if (ev->key.keyval == GDK_r) {
335 /* if we're not step editing, this really doesn't matter */
336 midi_view()->step_edit_rest ();
342 case GDK_KEY_RELEASE:
343 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
349 case GDK_BUTTON_PRESS:
350 last_x = ev->button.x;
351 last_y = ev->button.y;
352 group->w2i (last_x, last_y);
354 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
355 _pressed_button = ev->button.button;
356 _mouse_state = Pressed;
359 _pressed_button = ev->button.button;
362 case GDK_2BUTTON_PRESS:
365 case GDK_ENTER_NOTIFY:
366 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
367 Keyboard::magic_widget_grab_focus();
371 case GDK_MOTION_NOTIFY:
372 event_x = ev->motion.x;
373 event_y = ev->motion.y;
374 group->w2i(event_x, event_y);
376 // convert event_x to global frame
377 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
378 trackview.editor().snap_to(event_frame);
379 // convert event_frame back to local coordinates relative to position
380 event_frame -= _region->position();
382 switch (_mouse_state) {
383 case Pressed: // Maybe start a drag, if we've moved a bit
385 if (fabs (event_x - last_x) < 1 && fabs (event_y - last_y) < 1) {
386 /* no appreciable movement since the button was pressed */
391 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
392 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
393 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
396 drag_start_x = event_x;
397 drag_start_y = event_y;
399 drag_rect = new ArdourCanvas::SimpleRect(*group);
400 drag_rect->property_x1() = event_x;
401 drag_rect->property_y1() = event_y;
402 drag_rect->property_x2() = event_x;
403 drag_rect->property_y2() = event_y;
404 drag_rect->property_outline_what() = 0xFF;
405 drag_rect->property_outline_color_rgba()
406 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
407 drag_rect->property_fill_color_rgba()
408 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
410 _mouse_state = SelectRectDragging;
413 // Add note drag start
414 } else if (editor.internal_editing()) {
415 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
416 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
419 drag_start_x = event_x;
420 drag_start_y = event_y;
422 drag_rect = new ArdourCanvas::SimpleRect(*group);
423 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
425 drag_rect->property_y1() = midi_stream_view()->note_to_y(
426 midi_stream_view()->y_to_note(event_y));
427 drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
428 drag_rect->property_y2() = drag_rect->property_y1()
429 + floor(midi_stream_view()->note_height());
430 drag_rect->property_outline_what() = 0xFF;
431 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
432 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
434 _mouse_state = AddDragging;
440 case SelectRectDragging: // Select drag motion
441 case AddDragging: // Add note drag motion
442 if (ev->motion.is_hint) {
445 GdkModifierType state;
446 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
451 if (_mouse_state == AddDragging)
452 event_x = trackview.editor().frame_to_pixel(event_frame);
455 if (event_x > drag_start_x)
456 drag_rect->property_x2() = event_x;
458 drag_rect->property_x1() = event_x;
461 if (drag_rect && _mouse_state == SelectRectDragging) {
462 if (event_y > drag_start_y)
463 drag_rect->property_y2() = event_y;
465 drag_rect->property_y1() = event_y;
467 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
473 case SelectTouchDragging:
481 case GDK_BUTTON_RELEASE:
482 event_x = ev->motion.x;
483 event_y = ev->motion.y;
484 group->w2i(event_x, event_y);
485 group->ungrab(ev->button.time);
486 event_frame = trackview.editor().pixel_to_frame(event_x);
488 if (ev->button.button == 3) {
490 } else if (_pressed_button != 1) {
494 switch (_mouse_state) {
495 case Pressed: // Clicked
496 switch (editor.current_mouse_mode()) {
504 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
509 create_note_at (event_x, event_y, beats);
517 case SelectRectDragging: // Select drag done
522 case AddDragging: // Add drag done
524 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
525 const double x = drag_rect->property_x1();
526 const double length = trackview.editor().pixel_to_frame(
527 drag_rect->property_x2() - drag_rect->property_x1());
529 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
544 MidiRegionView::show_list_editor ()
547 _list_editor = new MidiListEditor (trackview.session(), midi_region());
549 _list_editor->present ();
552 /** Add a note to the model, and the view, at a canvas (click) coordinate.
553 * \param x horizontal position in pixels
554 * \param y vertical position in pixels
555 * \param length duration of the note in beats */
557 MidiRegionView::create_note_at(double x, double y, double length)
559 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
560 MidiStreamView* const view = mtv->midi_view();
562 double note = midi_stream_view()->y_to_note(y);
565 assert(note <= 127.0);
567 // Start of note in frames relative to region start
568 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
569 assert(start_frames >= 0);
572 length = frames_to_beats(
573 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
575 assert (length != 0);
577 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
578 frames_to_beats(start_frames + _region->start()), length,
579 (uint8_t)note, 0x40));
581 if (_model->contains (new_note)) {
585 view->update_note_range(new_note->note());
587 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
589 _model->apply_command(*trackview.session(), cmd);
591 play_midi_note (new_note);
595 MidiRegionView::clear_events()
600 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
601 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
606 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
611 _pgm_changes.clear();
613 _optimization_iterator = _events.end();
618 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
621 content_connection.disconnect ();
622 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
626 if (_enable_display) {
633 MidiRegionView::start_delta_command(string name)
635 if (!_delta_command) {
636 _delta_command = _model->new_delta_command(name);
641 MidiRegionView::start_diff_command(string name)
643 if (!_diff_command) {
644 _diff_command = _model->new_diff_command(name);
649 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
651 if (_delta_command) {
652 _delta_command->add(note);
655 _marked_for_selection.insert(note);
658 _marked_for_velocity.insert(note);
663 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
665 if (_delta_command && ev->note()) {
666 _delta_command->remove(ev->note());
671 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
672 MidiModel::DiffCommand::Property property,
676 _diff_command->change (ev->note(), property, val);
681 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
682 MidiModel::DiffCommand::Property property,
683 Evoral::MusicalTime val)
686 _diff_command->change (ev->note(), property, val);
691 MidiRegionView::apply_delta()
693 if (!_delta_command) {
697 // Mark all selected notes for selection when model reloads
698 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
699 _marked_for_selection.insert((*i)->note());
702 _model->apply_command(*trackview.session(), _delta_command);
704 midi_view()->midi_track()->playlist_modified();
706 _marked_for_selection.clear();
707 _marked_for_velocity.clear();
711 MidiRegionView::apply_diff ()
713 if (!_diff_command) {
717 _model->apply_command(*trackview.session(), _diff_command);
719 midi_view()->midi_track()->playlist_modified();
721 _marked_for_velocity.clear();
725 MidiRegionView::apply_delta_as_subcommand()
727 if (!_delta_command) {
731 // Mark all selected notes for selection when model reloads
732 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
733 _marked_for_selection.insert((*i)->note());
736 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
738 midi_view()->midi_track()->playlist_modified();
740 _marked_for_selection.clear();
741 _marked_for_velocity.clear();
745 MidiRegionView::apply_diff_as_subcommand()
747 if (!_diff_command) {
751 // Mark all selected notes for selection when model reloads
752 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
753 _marked_for_selection.insert((*i)->note());
756 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
758 midi_view()->midi_track()->playlist_modified();
760 _marked_for_selection.clear();
761 _marked_for_velocity.clear();
765 MidiRegionView::abort_command()
767 delete _delta_command;
769 delete _diff_command;
775 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
777 if (_optimization_iterator != _events.end()) {
778 ++_optimization_iterator;
781 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
782 return *_optimization_iterator;
785 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
786 if ((*_optimization_iterator)->note() == note) {
787 return *_optimization_iterator;
795 MidiRegionView::redisplay_model()
797 // Don't redisplay the model if we're currently recording and displaying that
803 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
807 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
811 MidiModel::ReadLock lock(_model->read_lock());
813 MidiModel::Notes& notes (_model->notes());
814 _optimization_iterator = _events.begin();
816 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
818 boost::shared_ptr<NoteType> note (*n);
819 CanvasNoteEvent* cne;
822 if (note_in_region_range (note, visible)) {
824 if ((cne = find_canvas_note (note)) != 0) {
831 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
833 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
845 add_note (note, visible);
850 if ((cne = find_canvas_note (note)) != 0) {
858 /* remove note items that are no longer valid */
860 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
861 if (!(*i)->valid ()) {
863 i = _events.erase (i);
870 display_program_changes();
872 _marked_for_selection.clear ();
873 _marked_for_velocity.clear ();
875 /* we may have caused _events to contain things out of order (e.g. if a note
876 moved earlier or later). we don't generally need them in time order, but
877 make a note that a sort is required for those cases that require it.
884 MidiRegionView::display_program_changes()
886 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
891 Glib::Mutex::Lock lock (control->list()->lock());
893 uint8_t channel = control->parameter().channel();
895 for (AutomationList::const_iterator event = control->list()->begin();
896 event != control->list()->end(); ++event) {
897 double event_time = (*event)->when;
898 double program_number = floor((*event)->value + 0.5);
900 // Get current value of bank select MSB at time of the program change
901 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
902 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
904 if (msb_control != 0) {
905 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
908 // Get current value of bank select LSB at time of the program change
909 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
910 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
912 if (lsb_control != 0) {
913 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
916 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
918 boost::shared_ptr<MIDI::Name::Patch> patch =
919 MIDI::Name::MidiPatchManager::instance().find_patch(
920 _model_name, _custom_device_mode, channel, patch_key);
922 PCEvent program_change(event_time, uint8_t(program_number), channel);
925 add_pgm_change(program_change, patch->name());
928 snprintf(buf, 4, "%d", int(program_number));
929 add_pgm_change(program_change, buf);
935 MidiRegionView::display_sysexes()
937 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
938 Evoral::MusicalTime time = (*i)->time();
943 for (uint32_t b = 0; b < (*i)->size(); ++b) {
944 str << int((*i)->buffer()[b]);
945 if (b != (*i)->size() -1) {
949 string text = str.str();
951 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
953 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
955 double height = midi_stream_view()->contents_height();
957 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
958 new CanvasSysEx(*this, *group, text, height, x, 1.0));
960 // Show unless program change is beyond the region bounds
961 if (time - _region->start() >= _region->length() || time < _region->start()) {
967 _sys_exes.push_back(sysex);
972 MidiRegionView::~MidiRegionView ()
974 in_destructor = true;
976 note_delete_connection.disconnect ();
980 RegionViewGoingAway (this); /* EMIT_SIGNAL */
989 delete _delta_command;
993 MidiRegionView::region_resized (const PropertyChange& what_changed)
995 RegionView::region_resized(what_changed);
997 if (what_changed.contains (ARDOUR::Properties::position)) {
998 set_duration(_region->length(), 0);
999 if (_enable_display) {
1006 MidiRegionView::reset_width_dependent_items (double pixel_width)
1008 RegionView::reset_width_dependent_items(pixel_width);
1009 assert(_pixel_width == pixel_width);
1011 if (_enable_display) {
1017 MidiRegionView::set_height (double height)
1019 static const double FUDGE = 2.0;
1020 const double old_height = _height;
1021 RegionView::set_height(height);
1022 _height = height - FUDGE;
1024 apply_note_range(midi_stream_view()->lowest_note(),
1025 midi_stream_view()->highest_note(),
1026 height != old_height + FUDGE);
1029 name_pixbuf->raise_to_top();
1034 /** Apply the current note range from the stream view
1035 * by repositioning/hiding notes as necessary
1038 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1040 if (!_enable_display) {
1044 if (!force && _current_range_min == min && _current_range_max == max) {
1048 _current_range_min = min;
1049 _current_range_max = max;
1051 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1052 CanvasNoteEvent* event = *i;
1053 boost::shared_ptr<NoteType> note (event->note());
1055 if (note->note() < _current_range_min ||
1056 note->note() > _current_range_max) {
1062 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1064 const double y1 = midi_stream_view()->note_to_y(note->note());
1065 const double y2 = y1 + floor(midi_stream_view()->note_height());
1067 cnote->property_y1() = y1;
1068 cnote->property_y2() = y2;
1070 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1072 double x = trackview.editor().frame_to_pixel(
1073 beats_to_frames(note->time()) - _region->start());
1074 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1075 double y = midi_stream_view()->note_to_y(event->note()->note())
1076 + ((diamond_size-2.0) / 4.0);
1078 chit->set_height (diamond_size);
1079 chit->move (x - chit->x1(), y - chit->y1());
1086 MidiRegionView::add_ghost (TimeAxisView& tv)
1090 double unit_position = _region->position () / samples_per_unit;
1091 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1092 MidiGhostRegion* ghost;
1094 if (mtv && mtv->midi_view()) {
1095 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1096 to allow having midi notes on top of note lines and waveforms.
1098 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1100 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1103 ghost->set_height ();
1104 ghost->set_duration (_region->length() / samples_per_unit);
1105 ghosts.push_back (ghost);
1107 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1108 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1109 ghost->add_note(note);
1113 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1119 /** Begin tracking note state for successive calls to add_event
1122 MidiRegionView::begin_write()
1124 assert(!_active_notes);
1125 _active_notes = new CanvasNote*[128];
1126 for (unsigned i=0; i < 128; ++i) {
1127 _active_notes[i] = 0;
1132 /** Destroy note state for add_event
1135 MidiRegionView::end_write()
1137 delete[] _active_notes;
1139 _marked_for_selection.clear();
1140 _marked_for_velocity.clear();
1144 /** Resolve an active MIDI note (while recording).
1147 MidiRegionView::resolve_note(uint8_t note, double end_time)
1149 if (midi_view()->note_mode() != Sustained) {
1153 if (_active_notes && _active_notes[note]) {
1154 const nframes64_t end_time_frames = beats_to_frames(end_time);
1155 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1156 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1157 _active_notes[note] = 0;
1162 /** Extend active notes to rightmost edge of region (if length is changed)
1165 MidiRegionView::extend_active_notes()
1167 if (!_active_notes) {
1171 for (unsigned i=0; i < 128; ++i) {
1172 if (_active_notes[i]) {
1173 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1179 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1181 if (no_sound_notes || !trackview.editor().sound_notes()) {
1185 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1188 route_ui->midi_track()->write_immediate_event(
1189 note->on_event().size(), note->on_event().buffer());
1191 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1192 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1193 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1194 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1195 note_length_ms, G_PRIORITY_DEFAULT);
1199 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1201 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1204 route_ui->midi_track()->write_immediate_event(
1205 note->off_event().size(), note->off_event().buffer());
1211 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1213 const nframes64_t note_start_frames = beats_to_frames(note->time());
1215 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1216 (note_start_frames < _region->start());
1218 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1219 (note->note() <= midi_stream_view()->highest_note());
1225 MidiRegionView::update_note (CanvasNote* ev)
1227 boost::shared_ptr<NoteType> note = ev->note();
1229 const nframes64_t note_start_frames = beats_to_frames(note->time());
1230 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1232 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1233 const double y1 = midi_stream_view()->note_to_y(note->note());
1234 const double note_endpixel =
1235 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1237 ev->property_x1() = x;
1238 ev->property_y1() = y1;
1239 if (note->length() > 0) {
1240 ev->property_x2() = note_endpixel;
1242 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1244 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1246 if (note->length() == 0) {
1247 if (_active_notes) {
1248 assert(note->note() < 128);
1249 // If this note is already active there's a stuck note,
1250 // finish the old note rectangle
1251 if (_active_notes[note->note()]) {
1252 CanvasNote* const old_rect = _active_notes[note->note()];
1253 boost::shared_ptr<NoteType> old_note = old_rect->note();
1254 old_rect->property_x2() = x;
1255 old_rect->property_outline_what() = (guint32) 0xF;
1257 _active_notes[note->note()] = ev;
1259 /* outline all but right edge */
1260 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1262 /* outline all edges */
1263 ev->property_outline_what() = (guint32) 0xF;
1268 MidiRegionView::update_hit (CanvasHit* ev)
1270 boost::shared_ptr<NoteType> note = ev->note();
1272 const nframes64_t note_start_frames = beats_to_frames(note->time());
1273 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1274 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1275 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1280 /** Add a MIDI note to the view (with length).
1282 * If in sustained mode, notes with length 0 will be considered active
1283 * notes, and resolve_note should be called when the corresponding note off
1284 * event arrives, to properly display the note.
1287 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1289 CanvasNoteEvent* event = 0;
1291 assert(note->time() >= 0);
1292 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1294 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1296 if (midi_view()->note_mode() == Sustained) {
1298 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1300 update_note (ev_rect);
1304 MidiGhostRegion* gr;
1306 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1307 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1308 gr->add_note(ev_rect);
1312 } else if (midi_view()->note_mode() == Percussive) {
1314 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1316 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1318 update_hit (ev_diamond);
1327 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1328 note_selected(event, true);
1331 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1332 event->show_velocity();
1334 event->on_channel_selection_change(_last_channel_selection);
1335 _events.push_back(event);
1346 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1347 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1349 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1351 start_delta_command (_("step add"));
1352 delta_add_note (new_note, true, false);
1355 /* potentially extend region to hold new note */
1357 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1358 nframes64_t region_end = _region->position() + _region->length() - 1;
1360 if (end_frame > region_end) {
1361 _region->set_length (end_frame, this);
1368 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1370 assert(program.time >= 0);
1372 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1373 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1375 double height = midi_stream_view()->contents_height();
1377 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1378 new CanvasProgramChange(*this, *group,
1383 _custom_device_mode,
1384 program.time, program.channel, program.value));
1386 // Show unless program change is beyond the region bounds
1387 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1393 _pgm_changes.push_back(pgm_change);
1397 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1399 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1400 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1401 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1403 if (msb_control != 0) {
1404 msb = int(msb_control->get_float(true, time));
1405 cerr << "got msb " << msb;
1408 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1409 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1411 if (lsb_control != 0) {
1412 lsb = lsb_control->get_float(true, time);
1413 cerr << " got lsb " << lsb;
1416 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1417 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1418 float program_number = -1.0;
1419 if (program_control != 0) {
1420 program_number = program_control->get_float(true, time);
1421 cerr << " got program " << program_number << endl;
1424 key.msb = (int) floor(msb + 0.5);
1425 key.lsb = (int) floor(lsb + 0.5);
1426 key.program_number = (int) floor(program_number + 0.5);
1427 assert(key.is_sane());
1432 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1434 // TODO: Get the real event here and alter them at the original times
1435 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1436 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1437 if (msb_control != 0) {
1438 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1441 // TODO: Get the real event here and alter them at the original times
1442 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1443 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1444 if (lsb_control != 0) {
1445 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1448 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1449 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1451 assert(program_control != 0);
1452 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1458 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1460 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1461 alter_program_change(program_change_event, new_patch);
1465 MidiRegionView::previous_program(CanvasProgramChange& program)
1467 MIDI::Name::PatchPrimaryKey key;
1468 get_patch_key_at(program.event_time(), program.channel(), key);
1470 boost::shared_ptr<MIDI::Name::Patch> patch =
1471 MIDI::Name::MidiPatchManager::instance().previous_patch(
1473 _custom_device_mode,
1477 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1479 alter_program_change(program_change_event, patch->patch_primary_key());
1484 MidiRegionView::next_program(CanvasProgramChange& program)
1486 MIDI::Name::PatchPrimaryKey key;
1487 get_patch_key_at(program.event_time(), program.channel(), key);
1489 boost::shared_ptr<MIDI::Name::Patch> patch =
1490 MIDI::Name::MidiPatchManager::instance().next_patch(
1492 _custom_device_mode,
1496 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1498 alter_program_change(program_change_event, patch->patch_primary_key());
1503 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1505 if (_selection.empty()) {
1509 if (_selection.erase (cne) > 0) {
1510 cerr << "Erased a CNE from selection\n";
1515 MidiRegionView::delete_selection()
1517 if (_selection.empty()) {
1521 start_delta_command (_("delete selection"));
1523 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1524 if ((*i)->selected()) {
1525 _delta_command->remove((*i)->note());
1535 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1537 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1538 if ((*i)->selected() && (*i) != ev) {
1539 (*i)->selected(false);
1540 (*i)->hide_velocity();
1548 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1550 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1553 Selection::iterator tmp = i;
1556 (*i)->selected (false);
1557 _selection.erase (i);
1566 /* don't bother with removing this regionview from the editor selection,
1567 since we're about to add another note, and thus put/keep this
1568 regionview in the editor selection.
1571 if (!ev->selected()) {
1572 add_to_selection (ev);
1577 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1579 uint8_t low_note = 127;
1580 uint8_t high_note = 0;
1581 MidiModel::Notes& notes (_model->notes());
1582 _optimization_iterator = _events.begin();
1584 if (extend && _selection.empty()) {
1590 /* scan existing selection to get note range */
1592 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1593 if ((*i)->note()->note() < low_note) {
1594 low_note = (*i)->note()->note();
1596 if ((*i)->note()->note() > high_note) {
1597 high_note = (*i)->note()->note();
1601 low_note = min (low_note, notenum);
1602 high_note = max (high_note, notenum);
1605 no_sound_notes = true;
1607 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1609 boost::shared_ptr<NoteType> note (*n);
1610 CanvasNoteEvent* cne;
1611 bool select = false;
1613 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1615 if ((note->note() >= low_note && note->note() <= high_note)) {
1618 } else if (note->note() == notenum) {
1624 if ((cne = find_canvas_note (note)) != 0) {
1625 // extend is false because we've taken care of it,
1626 // since it extends by time range, not pitch.
1627 note_selected (cne, add, false);
1631 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1635 no_sound_notes = false;
1639 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1641 MidiModel::Notes& notes (_model->notes());
1642 _optimization_iterator = _events.begin();
1644 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1646 boost::shared_ptr<NoteType> note (*n);
1647 CanvasNoteEvent* cne;
1649 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1650 if ((cne = find_canvas_note (note)) != 0) {
1651 if (cne->selected()) {
1652 note_deselected (cne);
1654 note_selected (cne, true, false);
1662 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1665 clear_selection_except(ev);
1670 if (!ev->selected()) {
1671 add_to_selection (ev);
1675 /* find end of latest note selected, select all between that and the start of "ev" */
1677 Evoral::MusicalTime earliest = DBL_MAX;
1678 Evoral::MusicalTime latest = 0;
1680 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1681 if ((*i)->note()->end_time() > latest) {
1682 latest = (*i)->note()->end_time();
1684 if ((*i)->note()->time() < earliest) {
1685 earliest = (*i)->note()->time();
1689 if (ev->note()->end_time() > latest) {
1690 latest = ev->note()->end_time();
1693 if (ev->note()->time() < earliest) {
1694 earliest = ev->note()->time();
1697 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1699 /* find notes entirely within OR spanning the earliest..latest range */
1701 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1702 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1703 add_to_selection (*i);
1707 /* if events were guaranteed to be time sorted, we could do this.
1708 but as of sept 10th 2009, they no longer are.
1711 if ((*i)->note()->time() > latest) {
1720 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1722 remove_from_selection (ev);
1726 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1736 // TODO: Make this faster by storing the last updated selection rect, and only
1737 // adjusting things that are in the area that appears/disappeared.
1738 // We probably need a tree to be able to find events in O(log(n)) time.
1740 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1742 /* check if any corner of the note is inside the rect
1745 1) this is computing "touched by", not "contained by" the rect.
1746 2) this does not require that events be sorted in time.
1749 const double ix1 = (*i)->x1();
1750 const double ix2 = (*i)->x2();
1751 const double iy1 = (*i)->y1();
1752 const double iy2 = (*i)->y2();
1754 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1755 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1756 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1757 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1760 if (!(*i)->selected()) {
1761 add_to_selection (*i);
1763 } else if ((*i)->selected()) {
1764 // Not inside rectangle
1765 remove_from_selection (*i);
1771 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1773 Selection::iterator i = _selection.find (ev);
1775 if (i != _selection.end()) {
1776 _selection.erase (i);
1779 ev->selected (false);
1780 ev->hide_velocity ();
1782 if (_selection.empty()) {
1783 PublicEditor& editor (trackview.editor());
1784 editor.get_selection().remove (this);
1789 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1791 bool add_mrv_selection = false;
1793 if (_selection.empty()) {
1794 add_mrv_selection = true;
1797 if (_selection.insert (ev).second) {
1798 ev->selected (true);
1799 play_midi_note ((ev)->note());
1802 if (add_mrv_selection) {
1803 PublicEditor& editor (trackview.editor());
1804 editor.get_selection().add (this);
1809 MidiRegionView::move_selection(double dx, double dy)
1811 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1812 (*i)->move_event(dx, dy);
1817 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1819 assert (!_selection.empty());
1821 uint8_t lowest_note_in_selection = 127;
1822 uint8_t highest_note_in_selection = 0;
1823 uint8_t highest_note_difference = 0;
1825 // find highest and lowest notes first
1827 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1828 uint8_t pitch = (*i)->note()->note();
1829 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1830 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1834 cerr << "dnote: " << (int) dnote << endl;
1835 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1836 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1837 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1838 << int(highest_note_in_selection) << endl;
1839 cerr << "selection size: " << _selection.size() << endl;
1840 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1843 // Make sure the note pitch does not exceed the MIDI standard range
1844 if (highest_note_in_selection + dnote > 127) {
1845 highest_note_difference = highest_note_in_selection - 127;
1848 start_diff_command(_("move notes"));
1850 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1852 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1855 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1857 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1860 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1866 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1868 uint8_t original_pitch = (*i)->note()->note();
1869 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1871 // keep notes in standard midi range
1872 clamp_to_0_127(new_pitch);
1874 // keep original pitch if note is dragged outside valid midi range
1875 if ((original_pitch != 0 && new_pitch == 0)
1876 || (original_pitch != 127 && new_pitch == 127)) {
1877 new_pitch = original_pitch;
1880 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1881 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1883 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1888 // care about notes being moved beyond the upper/lower bounds on the canvas
1889 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1890 highest_note_in_selection > midi_stream_view()->highest_note()) {
1891 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1896 MidiRegionView::snap_pixel_to_frame(double x)
1898 PublicEditor& editor = trackview.editor();
1899 // x is region relative, convert it to global absolute frames
1900 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1901 editor.snap_to(frame);
1902 return frame - _region->position(); // convert back to region relative
1906 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1908 PublicEditor& editor = trackview.editor();
1909 // x is region relative, convert it to global absolute frames
1910 nframes64_t frame = x + _region->position();
1911 editor.snap_to(frame);
1912 return frame - _region->position(); // convert back to region relative
1916 MidiRegionView::snap_to_pixel(double x)
1918 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1922 MidiRegionView::get_position_pixels()
1924 nframes64_t region_frame = get_position();
1925 return trackview.editor().frame_to_pixel(region_frame);
1929 MidiRegionView::get_end_position_pixels()
1931 nframes64_t frame = get_position() + get_duration ();
1932 return trackview.editor().frame_to_pixel(frame);
1936 MidiRegionView::beats_to_frames(double beats) const
1938 return _time_converter.to(beats);
1942 MidiRegionView::frames_to_beats(nframes64_t frames) const
1944 return _time_converter.from(frames);
1948 MidiRegionView::begin_resizing (bool /*at_front*/)
1950 _resize_data.clear();
1952 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1953 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1955 // only insert CanvasNotes into the map
1957 NoteResizeData *resize_data = new NoteResizeData();
1958 resize_data->canvas_note = note;
1960 // create a new SimpleRect from the note which will be the resize preview
1961 SimpleRect *resize_rect = new SimpleRect(
1962 *group, note->x1(), note->y1(), note->x2(), note->y2());
1964 // calculate the colors: get the color settings
1965 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1966 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1969 // make the resize preview notes more transparent and bright
1970 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1972 // calculate color based on note velocity
1973 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1974 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1978 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1979 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1981 resize_data->resize_rect = resize_rect;
1982 _resize_data.push_back(resize_data);
1988 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1990 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1991 SimpleRect* resize_rect = (*i)->resize_rect;
1992 CanvasNote* canvas_note = (*i)->canvas_note;
1997 current_x = canvas_note->x1() + delta_x;
1999 // x is in track relative, transform it to region relative
2000 current_x = delta_x - get_position_pixels();
2004 current_x = canvas_note->x2() + delta_x;
2006 // x is in track relative, transform it to region relative
2007 current_x = delta_x - get_end_position_pixels ();
2012 resize_rect->property_x1() = snap_to_pixel(current_x);
2013 resize_rect->property_x2() = canvas_note->x2();
2015 resize_rect->property_x2() = snap_to_pixel(current_x);
2016 resize_rect->property_x1() = canvas_note->x1();
2022 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
2024 start_diff_command(_("resize notes"));
2026 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2027 CanvasNote* canvas_note = (*i)->canvas_note;
2028 SimpleRect* resize_rect = (*i)->resize_rect;
2029 const double region_start = get_position_pixels();
2034 current_x = canvas_note->x1() + delta_x;
2036 // x is in track relative, transform it to region relative
2037 current_x = region_start + delta_x;
2041 current_x = canvas_note->x2() + delta_x;
2043 // x is in track relative, transform it to region relative
2044 current_x = region_start + delta_x;
2048 current_x = snap_pixel_to_frame (current_x);
2049 current_x = frames_to_beats (current_x);
2051 if (at_front && current_x < canvas_note->note()->end_time()) {
2052 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2054 double len = canvas_note->note()->time() - current_x;
2055 len += canvas_note->note()->length();
2058 /* XXX convert to beats */
2059 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2064 double len = current_x - canvas_note->note()->time();
2067 /* XXX convert to beats */
2068 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2076 _resize_data.clear();
2081 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2083 uint8_t new_velocity;
2086 new_velocity = event->note()->velocity() + velocity;
2087 clamp_to_0_127(new_velocity);
2089 new_velocity = velocity;
2092 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2096 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2101 new_note = event->note()->note() + note;
2106 clamp_to_0_127 (new_note);
2107 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2111 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2113 bool change_start = false;
2114 bool change_length = false;
2115 Evoral::MusicalTime new_start;
2116 Evoral::MusicalTime new_length;
2118 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2120 front_delta: if positive - move the start of the note later in time (shortening it)
2121 if negative - move the start of the note earlier in time (lengthening it)
2123 end_delta: if positive - move the end of the note later in time (lengthening it)
2124 if negative - move the end of the note earlier in time (shortening it)
2128 if (front_delta < 0) {
2130 if (event->note()->time() < -front_delta) {
2133 new_start = event->note()->time() + front_delta; // moves earlier
2136 /* start moved toward zero, so move the end point out to where it used to be.
2137 Note that front_delta is negative, so this increases the length.
2140 new_length = event->note()->length() - front_delta;
2141 change_start = true;
2142 change_length = true;
2146 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2148 if (new_pos < event->note()->end_time()) {
2149 new_start = event->note()->time() + front_delta;
2150 /* start moved toward the end, so move the end point back to where it used to be */
2151 new_length = event->note()->length() - front_delta;
2152 change_start = true;
2153 change_length = true;
2160 bool can_change = true;
2161 if (end_delta < 0) {
2162 if (event->note()->length() < -end_delta) {
2168 new_length = event->note()->length() + end_delta;
2169 change_length = true;
2174 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2177 if (change_length) {
2178 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2183 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2185 Evoral::MusicalTime new_time;
2189 if (event->note()->time() < -delta) {
2192 new_time = event->note()->time() + delta;
2195 new_time = event->note()->time() + delta;
2201 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2205 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2209 if (_selection.empty()) {
2224 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2225 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2231 start_diff_command(_("change velocities"));
2233 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2234 Selection::iterator next = i;
2236 change_note_velocity (*i, delta, true);
2245 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2247 if (_selection.empty()) {
2264 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2266 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2270 if ((int8_t) (*i)->note()->note() + delta > 127) {
2277 start_diff_command (_("transpose"));
2279 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2280 Selection::iterator next = i;
2282 change_note_note (*i, delta, true);
2290 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2292 Evoral::MusicalTime delta;
2297 /* grab the current grid distance */
2299 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2301 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2302 cerr << "Grid type not available as beats - TO BE FIXED\n";
2311 start_diff_command (_("change note lengths"));
2313 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2314 Selection::iterator next = i;
2317 /* note the negation of the delta for start */
2319 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2328 MidiRegionView::nudge_notes (bool forward)
2330 if (_selection.empty()) {
2334 /* pick a note as the point along the timeline to get the nudge distance.
2335 its not necessarily the earliest note, so we may want to pull the notes out
2336 into a vector and sort before using the first one.
2339 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2341 nframes64_t distance;
2343 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2345 /* grid is off - use nudge distance */
2347 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2353 nframes64_t next_pos = ref_point;
2356 /* XXX need check on max_frames, but that needs max_frames64 or something */
2359 if (next_pos == 0) {
2365 cerr << "ref point was " << ref_point << " next was " << next_pos;
2366 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2367 distance = ref_point - next_pos;
2368 cerr << " final is " << next_pos << " distance = " << distance << endl;
2371 if (distance == 0) {
2375 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2381 start_diff_command (_("nudge"));
2383 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2384 Selection::iterator next = i;
2386 change_note_time (*i, delta, true);
2394 MidiRegionView::change_channel(uint8_t channel)
2396 start_diff_command(_("change channel"));
2397 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2398 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2405 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2407 if (_mouse_state == SelectTouchDragging) {
2408 note_selected(ev, true);
2412 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (ev->note()->note()).c_str(), (int) ev->note()->note());
2413 PublicEditor& editor (trackview.editor());
2414 editor.show_verbose_canvas_cursor_with (buf);
2418 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2420 PublicEditor& editor (trackview.editor());
2421 editor.hide_verbose_canvas_cursor ();
2426 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2428 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2430 display_model(msrc->model());
2434 MidiRegionView::set_frame_color()
2437 if (_selected && should_show_selection) {
2438 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2440 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2446 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2450 case FilterChannels:
2451 _force_channel = -1;
2454 _force_channel = mask;
2455 mask = 0xFFFF; // Show all notes as active (below)
2458 // Update notes for selection
2459 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2460 (*i)->on_channel_selection_change(mask);
2463 _last_channel_selection = mask;
2467 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2469 _model_name = model;
2470 _custom_device_mode = custom_device_mode;
2475 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2477 if (_selection.empty()) {
2481 PublicEditor& editor (trackview.editor());
2486 editor.get_cut_buffer().add (selection_as_cut_buffer());
2494 start_delta_command();
2496 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2502 delta_remove_note (*i);
2512 MidiRegionView::selection_as_cut_buffer () const
2516 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2517 NoteType* n = (*i)->note().get();
2518 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2521 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2528 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2534 start_delta_command (_("paste"));
2536 Evoral::MusicalTime beat_delta;
2537 Evoral::MusicalTime paste_pos_beats;
2538 Evoral::MusicalTime duration;
2539 Evoral::MusicalTime end_point;
2541 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2542 paste_pos_beats = frames_to_beats (pos - _region->position());
2543 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2544 paste_pos_beats = 0;
2546 _selection.clear ();
2548 for (int n = 0; n < (int) times; ++n) {
2550 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2552 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2554 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2555 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2557 /* make all newly added notes selected */
2559 delta_add_note (copied_note, true);
2560 end_point = copied_note->end_time();
2563 paste_pos_beats += duration;
2566 /* if we pasted past the current end of the region, extend the region */
2568 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2569 nframes64_t region_end = _region->position() + _region->length() - 1;
2571 if (end_frame > region_end) {
2573 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2575 trackview.session()->begin_reversible_command (_("paste"));
2577 _region->clear_history ();
2578 _region->set_length (end_frame, this);
2579 trackview.session()->add_command (new StatefulDiffCommand (_region));
2582 cerr << "region end finally at " << _region->position() + _region->length() - 1;
2586 struct EventNoteTimeEarlyFirstComparator {
2587 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2588 return a->note()->time() < b->note()->time();
2593 MidiRegionView::time_sort_events ()
2595 if (!_sort_needed) {
2599 EventNoteTimeEarlyFirstComparator cmp;
2602 _sort_needed = false;
2606 MidiRegionView::goto_next_note ()
2608 // nframes64_t pos = -1;
2609 bool use_next = false;
2611 if (_events.back()->selected()) {
2615 time_sort_events ();
2617 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2618 if ((*i)->selected()) {
2621 } else if (use_next) {
2623 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2628 /* use the first one */
2630 unique_select (_events.front());
2635 MidiRegionView::goto_previous_note ()
2637 // nframes64_t pos = -1;
2638 bool use_next = false;
2640 if (_events.front()->selected()) {
2644 time_sort_events ();
2646 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2647 if ((*i)->selected()) {
2650 } else if (use_next) {
2652 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2657 /* use the last one */
2659 unique_select (*(_events.rbegin()));
2663 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2665 bool had_selected = false;
2667 time_sort_events ();
2669 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2670 if ((*i)->selected()) {
2671 selected.insert ((*i)->note());
2672 had_selected = true;
2676 if (allow_all_if_none_selected && !had_selected) {
2677 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2678 selected.insert ((*i)->note());