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.current_mouse_mode() == MouseRange) {
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() = event_x;
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 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 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
576 frames_to_beats(start_frames + _region->start()), length,
577 (uint8_t)note, 0x40));
579 if (_model->contains (new_note)) {
583 view->update_note_range(new_note->note());
585 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
587 _model->apply_command(*trackview.session(), cmd);
589 play_midi_note (new_note);
593 MidiRegionView::clear_events()
598 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
599 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
604 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
609 _pgm_changes.clear();
611 _optimization_iterator = _events.end();
616 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
619 content_connection.disconnect ();
620 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
624 if (_enable_display) {
631 MidiRegionView::start_delta_command(string name)
633 if (!_delta_command) {
634 _delta_command = _model->new_delta_command(name);
639 MidiRegionView::start_diff_command(string name)
641 if (!_diff_command) {
642 _diff_command = _model->new_diff_command(name);
647 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
649 if (_delta_command) {
650 _delta_command->add(note);
653 _marked_for_selection.insert(note);
656 _marked_for_velocity.insert(note);
661 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
663 if (_delta_command && ev->note()) {
664 _delta_command->remove(ev->note());
669 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
670 MidiModel::DiffCommand::Property property,
674 _diff_command->change (ev->note(), property, val);
679 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
680 MidiModel::DiffCommand::Property property,
681 Evoral::MusicalTime val)
684 _diff_command->change (ev->note(), property, val);
689 MidiRegionView::apply_delta()
691 if (!_delta_command) {
695 // Mark all selected notes for selection when model reloads
696 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
697 _marked_for_selection.insert((*i)->note());
700 _model->apply_command(*trackview.session(), _delta_command);
702 midi_view()->midi_track()->playlist_modified();
704 _marked_for_selection.clear();
705 _marked_for_velocity.clear();
709 MidiRegionView::apply_diff ()
711 if (!_diff_command) {
715 _model->apply_command(*trackview.session(), _diff_command);
717 midi_view()->midi_track()->playlist_modified();
719 _marked_for_velocity.clear();
723 MidiRegionView::apply_delta_as_subcommand()
725 if (!_delta_command) {
729 // Mark all selected notes for selection when model reloads
730 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
731 _marked_for_selection.insert((*i)->note());
734 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
736 midi_view()->midi_track()->playlist_modified();
738 _marked_for_selection.clear();
739 _marked_for_velocity.clear();
743 MidiRegionView::apply_diff_as_subcommand()
745 if (!_diff_command) {
749 // Mark all selected notes for selection when model reloads
750 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
751 _marked_for_selection.insert((*i)->note());
754 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
756 midi_view()->midi_track()->playlist_modified();
758 _marked_for_selection.clear();
759 _marked_for_velocity.clear();
763 MidiRegionView::abort_command()
765 delete _delta_command;
767 delete _diff_command;
773 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
775 if (_optimization_iterator != _events.end()) {
776 ++_optimization_iterator;
779 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
780 return *_optimization_iterator;
783 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
784 if ((*_optimization_iterator)->note() == note) {
785 return *_optimization_iterator;
793 MidiRegionView::redisplay_model()
795 // Don't redisplay the model if we're currently recording and displaying that
801 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
805 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
809 MidiModel::ReadLock lock(_model->read_lock());
811 MidiModel::Notes& notes (_model->notes());
812 _optimization_iterator = _events.begin();
814 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
816 boost::shared_ptr<NoteType> note (*n);
817 CanvasNoteEvent* cne;
820 if (note_in_region_range (note, visible)) {
822 if ((cne = find_canvas_note (note)) != 0) {
829 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
831 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
843 add_note (note, visible);
848 if ((cne = find_canvas_note (note)) != 0) {
856 /* remove note items that are no longer valid */
858 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
859 if (!(*i)->valid ()) {
861 i = _events.erase (i);
868 display_program_changes();
870 _marked_for_selection.clear ();
871 _marked_for_velocity.clear ();
873 /* we may have caused _events to contain things out of order (e.g. if a note
874 moved earlier or later). we don't generally need them in time order, but
875 make a note that a sort is required for those cases that require it.
882 MidiRegionView::display_program_changes()
884 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
889 Glib::Mutex::Lock lock (control->list()->lock());
891 uint8_t channel = control->parameter().channel();
893 for (AutomationList::const_iterator event = control->list()->begin();
894 event != control->list()->end(); ++event) {
895 double event_time = (*event)->when;
896 double program_number = floor((*event)->value + 0.5);
898 // Get current value of bank select MSB at time of the program change
899 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
900 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
902 if (msb_control != 0) {
903 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
906 // Get current value of bank select LSB at time of the program change
907 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
908 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
910 if (lsb_control != 0) {
911 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
914 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
916 boost::shared_ptr<MIDI::Name::Patch> patch =
917 MIDI::Name::MidiPatchManager::instance().find_patch(
918 _model_name, _custom_device_mode, channel, patch_key);
920 PCEvent program_change(event_time, uint8_t(program_number), channel);
923 add_pgm_change(program_change, patch->name());
926 snprintf(buf, 4, "%d", int(program_number));
927 add_pgm_change(program_change, buf);
933 MidiRegionView::display_sysexes()
935 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
936 Evoral::MusicalTime time = (*i)->time();
941 for (uint32_t b = 0; b < (*i)->size(); ++b) {
942 str << int((*i)->buffer()[b]);
943 if (b != (*i)->size() -1) {
947 string text = str.str();
949 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
951 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
953 double height = midi_stream_view()->contents_height();
955 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
956 new CanvasSysEx(*this, *group, text, height, x, 1.0));
958 // Show unless program change is beyond the region bounds
959 if (time - _region->start() >= _region->length() || time < _region->start()) {
965 _sys_exes.push_back(sysex);
970 MidiRegionView::~MidiRegionView ()
972 in_destructor = true;
974 note_delete_connection.disconnect ();
978 RegionViewGoingAway (this); /* EMIT_SIGNAL */
987 delete _delta_command;
991 MidiRegionView::region_resized (const PropertyChange& what_changed)
993 RegionView::region_resized(what_changed);
995 if (what_changed.contains (ARDOUR::Properties::position)) {
996 set_duration(_region->length(), 0);
997 if (_enable_display) {
1004 MidiRegionView::reset_width_dependent_items (double pixel_width)
1006 RegionView::reset_width_dependent_items(pixel_width);
1007 assert(_pixel_width == pixel_width);
1009 if (_enable_display) {
1015 MidiRegionView::set_height (double height)
1017 static const double FUDGE = 2.0;
1018 const double old_height = _height;
1019 RegionView::set_height(height);
1020 _height = height - FUDGE;
1022 apply_note_range(midi_stream_view()->lowest_note(),
1023 midi_stream_view()->highest_note(),
1024 height != old_height + FUDGE);
1027 name_pixbuf->raise_to_top();
1032 /** Apply the current note range from the stream view
1033 * by repositioning/hiding notes as necessary
1036 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1038 if (!_enable_display) {
1042 if (!force && _current_range_min == min && _current_range_max == max) {
1046 _current_range_min = min;
1047 _current_range_max = max;
1049 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1050 CanvasNoteEvent* event = *i;
1051 boost::shared_ptr<NoteType> note (event->note());
1053 if (note->note() < _current_range_min ||
1054 note->note() > _current_range_max) {
1060 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1062 const double y1 = midi_stream_view()->note_to_y(note->note());
1063 const double y2 = y1 + floor(midi_stream_view()->note_height());
1065 cnote->property_y1() = y1;
1066 cnote->property_y2() = y2;
1068 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1070 double x = trackview.editor().frame_to_pixel(
1071 beats_to_frames(note->time()) - _region->start());
1072 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1073 double y = midi_stream_view()->note_to_y(event->note()->note())
1074 + ((diamond_size-2.0) / 4.0);
1076 chit->set_height (diamond_size);
1077 chit->move (x - chit->x1(), y - chit->y1());
1084 MidiRegionView::add_ghost (TimeAxisView& tv)
1088 double unit_position = _region->position () / samples_per_unit;
1089 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1090 MidiGhostRegion* ghost;
1092 if (mtv && mtv->midi_view()) {
1093 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1094 to allow having midi notes on top of note lines and waveforms.
1096 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1098 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1101 ghost->set_height ();
1102 ghost->set_duration (_region->length() / samples_per_unit);
1103 ghosts.push_back (ghost);
1105 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1106 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1107 ghost->add_note(note);
1111 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1117 /** Begin tracking note state for successive calls to add_event
1120 MidiRegionView::begin_write()
1122 assert(!_active_notes);
1123 _active_notes = new CanvasNote*[128];
1124 for (unsigned i=0; i < 128; ++i) {
1125 _active_notes[i] = 0;
1130 /** Destroy note state for add_event
1133 MidiRegionView::end_write()
1135 delete[] _active_notes;
1137 _marked_for_selection.clear();
1138 _marked_for_velocity.clear();
1142 /** Resolve an active MIDI note (while recording).
1145 MidiRegionView::resolve_note(uint8_t note, double end_time)
1147 if (midi_view()->note_mode() != Sustained) {
1151 if (_active_notes && _active_notes[note]) {
1152 const nframes64_t end_time_frames = beats_to_frames(end_time);
1153 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1154 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1155 _active_notes[note] = 0;
1160 /** Extend active notes to rightmost edge of region (if length is changed)
1163 MidiRegionView::extend_active_notes()
1165 if (!_active_notes) {
1169 for (unsigned i=0; i < 128; ++i) {
1170 if (_active_notes[i]) {
1171 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1177 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1179 if (no_sound_notes || !trackview.editor().sound_notes()) {
1183 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1186 route_ui->midi_track()->write_immediate_event(
1187 note->on_event().size(), note->on_event().buffer());
1189 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1190 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1191 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1192 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1193 note_length_ms, G_PRIORITY_DEFAULT);
1197 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1199 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1202 route_ui->midi_track()->write_immediate_event(
1203 note->off_event().size(), note->off_event().buffer());
1209 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1211 const nframes64_t note_start_frames = beats_to_frames(note->time());
1213 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1214 (note_start_frames < _region->start());
1216 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1217 (note->note() <= midi_stream_view()->highest_note());
1223 MidiRegionView::update_note (CanvasNote* ev)
1225 boost::shared_ptr<NoteType> note = ev->note();
1227 const nframes64_t note_start_frames = beats_to_frames(note->time());
1228 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1230 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1231 const double y1 = midi_stream_view()->note_to_y(note->note());
1232 const double note_endpixel =
1233 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1235 ev->property_x1() = x;
1236 ev->property_y1() = y1;
1237 if (note->length() > 0) {
1238 ev->property_x2() = note_endpixel;
1240 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1242 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1244 if (note->length() == 0) {
1245 if (_active_notes) {
1246 assert(note->note() < 128);
1247 // If this note is already active there's a stuck note,
1248 // finish the old note rectangle
1249 if (_active_notes[note->note()]) {
1250 CanvasNote* const old_rect = _active_notes[note->note()];
1251 boost::shared_ptr<NoteType> old_note = old_rect->note();
1252 old_rect->property_x2() = x;
1253 old_rect->property_outline_what() = (guint32) 0xF;
1255 _active_notes[note->note()] = ev;
1257 /* outline all but right edge */
1258 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1260 /* outline all edges */
1261 ev->property_outline_what() = (guint32) 0xF;
1266 MidiRegionView::update_hit (CanvasHit* ev)
1268 boost::shared_ptr<NoteType> note = ev->note();
1270 const nframes64_t note_start_frames = beats_to_frames(note->time());
1271 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1272 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1273 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1278 /** Add a MIDI note to the view (with length).
1280 * If in sustained mode, notes with length 0 will be considered active
1281 * notes, and resolve_note should be called when the corresponding note off
1282 * event arrives, to properly display the note.
1285 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1287 CanvasNoteEvent* event = 0;
1289 assert(note->time() >= 0);
1290 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1292 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1294 if (midi_view()->note_mode() == Sustained) {
1296 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1298 update_note (ev_rect);
1302 MidiGhostRegion* gr;
1304 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1305 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1306 gr->add_note(ev_rect);
1310 } else if (midi_view()->note_mode() == Percussive) {
1312 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1314 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1316 update_hit (ev_diamond);
1325 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1326 note_selected(event, true);
1329 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1330 event->show_velocity();
1332 event->on_channel_selection_change(_last_channel_selection);
1333 _events.push_back(event);
1344 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1345 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1347 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1349 start_delta_command (_("step add"));
1350 delta_add_note (new_note, true, false);
1353 /* potentially extend region to hold new note */
1355 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1356 nframes64_t region_end = _region->position() + _region->length() - 1;
1358 if (end_frame > region_end) {
1359 _region->set_length (end_frame, this);
1366 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1368 assert(program.time >= 0);
1370 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1371 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1373 double height = midi_stream_view()->contents_height();
1375 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1376 new CanvasProgramChange(*this, *group,
1381 _custom_device_mode,
1382 program.time, program.channel, program.value));
1384 // Show unless program change is beyond the region bounds
1385 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1391 _pgm_changes.push_back(pgm_change);
1395 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1397 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1398 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1399 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1401 if (msb_control != 0) {
1402 msb = int(msb_control->get_float(true, time));
1403 cerr << "got msb " << msb;
1406 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1407 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1409 if (lsb_control != 0) {
1410 lsb = lsb_control->get_float(true, time);
1411 cerr << " got lsb " << lsb;
1414 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1415 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1416 float program_number = -1.0;
1417 if (program_control != 0) {
1418 program_number = program_control->get_float(true, time);
1419 cerr << " got program " << program_number << endl;
1422 key.msb = (int) floor(msb + 0.5);
1423 key.lsb = (int) floor(lsb + 0.5);
1424 key.program_number = (int) floor(program_number + 0.5);
1425 assert(key.is_sane());
1430 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1432 // TODO: Get the real event here and alter them at the original times
1433 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1434 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1435 if (msb_control != 0) {
1436 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1439 // TODO: Get the real event here and alter them at the original times
1440 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1441 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1442 if (lsb_control != 0) {
1443 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1446 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1447 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1449 assert(program_control != 0);
1450 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1456 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1458 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1459 alter_program_change(program_change_event, new_patch);
1463 MidiRegionView::previous_program(CanvasProgramChange& program)
1465 MIDI::Name::PatchPrimaryKey key;
1466 get_patch_key_at(program.event_time(), program.channel(), key);
1468 boost::shared_ptr<MIDI::Name::Patch> patch =
1469 MIDI::Name::MidiPatchManager::instance().previous_patch(
1471 _custom_device_mode,
1475 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1477 alter_program_change(program_change_event, patch->patch_primary_key());
1482 MidiRegionView::next_program(CanvasProgramChange& program)
1484 MIDI::Name::PatchPrimaryKey key;
1485 get_patch_key_at(program.event_time(), program.channel(), key);
1487 boost::shared_ptr<MIDI::Name::Patch> patch =
1488 MIDI::Name::MidiPatchManager::instance().next_patch(
1490 _custom_device_mode,
1494 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1496 alter_program_change(program_change_event, patch->patch_primary_key());
1501 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1503 if (_selection.empty()) {
1507 if (_selection.erase (cne) > 0) {
1508 cerr << "Erased a CNE from selection\n";
1513 MidiRegionView::delete_selection()
1515 if (_selection.empty()) {
1519 start_delta_command (_("delete selection"));
1521 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1522 if ((*i)->selected()) {
1523 _delta_command->remove((*i)->note());
1533 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1535 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1536 if ((*i)->selected() && (*i) != ev) {
1537 (*i)->selected(false);
1538 (*i)->hide_velocity();
1546 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1548 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1551 Selection::iterator tmp = i;
1554 (*i)->selected (false);
1555 _selection.erase (i);
1564 /* don't bother with removing this regionview from the editor selection,
1565 since we're about to add another note, and thus put/keep this
1566 regionview in the editor selection.
1569 if (!ev->selected()) {
1570 add_to_selection (ev);
1575 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1577 uint8_t low_note = 127;
1578 uint8_t high_note = 0;
1579 MidiModel::Notes& notes (_model->notes());
1580 _optimization_iterator = _events.begin();
1582 if (extend && _selection.empty()) {
1588 /* scan existing selection to get note range */
1590 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1591 if ((*i)->note()->note() < low_note) {
1592 low_note = (*i)->note()->note();
1594 if ((*i)->note()->note() > high_note) {
1595 high_note = (*i)->note()->note();
1599 low_note = min (low_note, notenum);
1600 high_note = max (high_note, notenum);
1603 no_sound_notes = true;
1605 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1607 boost::shared_ptr<NoteType> note (*n);
1608 CanvasNoteEvent* cne;
1609 bool select = false;
1611 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1613 if ((note->note() >= low_note && note->note() <= high_note)) {
1616 } else if (note->note() == notenum) {
1622 if ((cne = find_canvas_note (note)) != 0) {
1623 // extend is false because we've taken care of it,
1624 // since it extends by time range, not pitch.
1625 note_selected (cne, add, false);
1629 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1633 no_sound_notes = false;
1637 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1639 MidiModel::Notes& notes (_model->notes());
1640 _optimization_iterator = _events.begin();
1642 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1644 boost::shared_ptr<NoteType> note (*n);
1645 CanvasNoteEvent* cne;
1647 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1648 if ((cne = find_canvas_note (note)) != 0) {
1649 if (cne->selected()) {
1650 note_deselected (cne);
1652 note_selected (cne, true, false);
1660 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1663 clear_selection_except(ev);
1668 if (!ev->selected()) {
1669 add_to_selection (ev);
1673 /* find end of latest note selected, select all between that and the start of "ev" */
1675 Evoral::MusicalTime earliest = DBL_MAX;
1676 Evoral::MusicalTime latest = 0;
1678 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1679 if ((*i)->note()->end_time() > latest) {
1680 latest = (*i)->note()->end_time();
1682 if ((*i)->note()->time() < earliest) {
1683 earliest = (*i)->note()->time();
1687 if (ev->note()->end_time() > latest) {
1688 latest = ev->note()->end_time();
1691 if (ev->note()->time() < earliest) {
1692 earliest = ev->note()->time();
1695 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1697 /* find notes entirely within OR spanning the earliest..latest range */
1699 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1700 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1701 add_to_selection (*i);
1705 /* if events were guaranteed to be time sorted, we could do this.
1706 but as of sept 10th 2009, they no longer are.
1709 if ((*i)->note()->time() > latest) {
1718 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1720 remove_from_selection (ev);
1724 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1734 // TODO: Make this faster by storing the last updated selection rect, and only
1735 // adjusting things that are in the area that appears/disappeared.
1736 // We probably need a tree to be able to find events in O(log(n)) time.
1738 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1740 /* check if any corner of the note is inside the rect
1743 1) this is computing "touched by", not "contained by" the rect.
1744 2) this does not require that events be sorted in time.
1747 const double ix1 = (*i)->x1();
1748 const double ix2 = (*i)->x2();
1749 const double iy1 = (*i)->y1();
1750 const double iy2 = (*i)->y2();
1752 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1753 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1754 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1755 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1758 if (!(*i)->selected()) {
1759 add_to_selection (*i);
1761 } else if ((*i)->selected()) {
1762 // Not inside rectangle
1763 remove_from_selection (*i);
1769 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1771 Selection::iterator i = _selection.find (ev);
1773 if (i != _selection.end()) {
1774 _selection.erase (i);
1777 ev->selected (false);
1778 ev->hide_velocity ();
1780 if (_selection.empty()) {
1781 PublicEditor& editor (trackview.editor());
1782 editor.get_selection().remove (this);
1787 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1789 bool add_mrv_selection = false;
1791 if (_selection.empty()) {
1792 add_mrv_selection = true;
1795 if (_selection.insert (ev).second) {
1796 ev->selected (true);
1797 play_midi_note ((ev)->note());
1800 if (add_mrv_selection) {
1801 PublicEditor& editor (trackview.editor());
1802 editor.get_selection().add (this);
1807 MidiRegionView::move_selection(double dx, double dy)
1809 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1810 (*i)->move_event(dx, dy);
1815 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1817 assert (!_selection.empty());
1819 uint8_t lowest_note_in_selection = 127;
1820 uint8_t highest_note_in_selection = 0;
1821 uint8_t highest_note_difference = 0;
1823 // find highest and lowest notes first
1825 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1826 uint8_t pitch = (*i)->note()->note();
1827 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1828 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1832 cerr << "dnote: " << (int) dnote << endl;
1833 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1834 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1835 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1836 << int(highest_note_in_selection) << endl;
1837 cerr << "selection size: " << _selection.size() << endl;
1838 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1841 // Make sure the note pitch does not exceed the MIDI standard range
1842 if (highest_note_in_selection + dnote > 127) {
1843 highest_note_difference = highest_note_in_selection - 127;
1846 start_diff_command(_("move notes"));
1848 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1850 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1853 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1855 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1858 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1864 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1866 uint8_t original_pitch = (*i)->note()->note();
1867 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1869 // keep notes in standard midi range
1870 clamp_to_0_127(new_pitch);
1872 // keep original pitch if note is dragged outside valid midi range
1873 if ((original_pitch != 0 && new_pitch == 0)
1874 || (original_pitch != 127 && new_pitch == 127)) {
1875 new_pitch = original_pitch;
1878 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1879 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1881 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1886 // care about notes being moved beyond the upper/lower bounds on the canvas
1887 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1888 highest_note_in_selection > midi_stream_view()->highest_note()) {
1889 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1894 MidiRegionView::snap_pixel_to_frame(double x)
1896 PublicEditor& editor = trackview.editor();
1897 // x is region relative, convert it to global absolute frames
1898 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1899 editor.snap_to(frame);
1900 return frame - _region->position(); // convert back to region relative
1904 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1906 PublicEditor& editor = trackview.editor();
1907 // x is region relative, convert it to global absolute frames
1908 nframes64_t frame = x + _region->position();
1909 editor.snap_to(frame);
1910 return frame - _region->position(); // convert back to region relative
1914 MidiRegionView::snap_to_pixel(double x)
1916 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1920 MidiRegionView::get_position_pixels()
1922 nframes64_t region_frame = get_position();
1923 return trackview.editor().frame_to_pixel(region_frame);
1927 MidiRegionView::get_end_position_pixels()
1929 nframes64_t frame = get_position() + get_duration ();
1930 return trackview.editor().frame_to_pixel(frame);
1934 MidiRegionView::beats_to_frames(double beats) const
1936 return _time_converter.to(beats);
1940 MidiRegionView::frames_to_beats(nframes64_t frames) const
1942 return _time_converter.from(frames);
1946 MidiRegionView::begin_resizing (bool /*at_front*/)
1948 _resize_data.clear();
1950 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1951 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1953 // only insert CanvasNotes into the map
1955 NoteResizeData *resize_data = new NoteResizeData();
1956 resize_data->canvas_note = note;
1958 // create a new SimpleRect from the note which will be the resize preview
1959 SimpleRect *resize_rect = new SimpleRect(
1960 *group, note->x1(), note->y1(), note->x2(), note->y2());
1962 // calculate the colors: get the color settings
1963 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1964 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1967 // make the resize preview notes more transparent and bright
1968 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1970 // calculate color based on note velocity
1971 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1972 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1976 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1977 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1979 resize_data->resize_rect = resize_rect;
1980 _resize_data.push_back(resize_data);
1986 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1988 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1989 SimpleRect* resize_rect = (*i)->resize_rect;
1990 CanvasNote* canvas_note = (*i)->canvas_note;
1995 current_x = canvas_note->x1() + delta_x;
1997 // x is in track relative, transform it to region relative
1998 current_x = delta_x - get_position_pixels();
2002 current_x = canvas_note->x2() + delta_x;
2004 // x is in track relative, transform it to region relative
2005 current_x = delta_x - get_end_position_pixels ();
2010 resize_rect->property_x1() = snap_to_pixel(current_x);
2011 resize_rect->property_x2() = canvas_note->x2();
2013 resize_rect->property_x2() = snap_to_pixel(current_x);
2014 resize_rect->property_x1() = canvas_note->x1();
2020 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
2022 start_diff_command(_("resize notes"));
2024 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2025 CanvasNote* canvas_note = (*i)->canvas_note;
2026 SimpleRect* resize_rect = (*i)->resize_rect;
2027 const double region_start = get_position_pixels();
2032 current_x = canvas_note->x1() + delta_x;
2034 // x is in track relative, transform it to region relative
2035 current_x = region_start + delta_x;
2039 current_x = canvas_note->x2() + delta_x;
2041 // x is in track relative, transform it to region relative
2042 current_x = region_start + delta_x;
2046 current_x = snap_pixel_to_frame (current_x);
2047 current_x = frames_to_beats (current_x);
2049 if (at_front && current_x < canvas_note->note()->end_time()) {
2050 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2052 double len = canvas_note->note()->time() - current_x;
2053 len += canvas_note->note()->length();
2056 /* XXX convert to beats */
2057 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2062 double len = current_x - canvas_note->note()->time();
2065 /* XXX convert to beats */
2066 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2074 _resize_data.clear();
2079 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2081 uint8_t new_velocity;
2084 new_velocity = event->note()->velocity() + velocity;
2085 clamp_to_0_127(new_velocity);
2087 new_velocity = velocity;
2090 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2094 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2099 new_note = event->note()->note() + note;
2104 clamp_to_0_127 (new_note);
2105 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2109 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2111 bool change_start = false;
2112 bool change_length = false;
2113 Evoral::MusicalTime new_start;
2114 Evoral::MusicalTime new_length;
2116 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2118 front_delta: if positive - move the start of the note later in time (shortening it)
2119 if negative - move the start of the note earlier in time (lengthening it)
2121 end_delta: if positive - move the end of the note later in time (lengthening it)
2122 if negative - move the end of the note earlier in time (shortening it)
2126 if (front_delta < 0) {
2128 if (event->note()->time() < -front_delta) {
2131 new_start = event->note()->time() + front_delta; // moves earlier
2134 /* start moved toward zero, so move the end point out to where it used to be.
2135 Note that front_delta is negative, so this increases the length.
2138 new_length = event->note()->length() - front_delta;
2139 change_start = true;
2140 change_length = true;
2144 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2146 if (new_pos < event->note()->end_time()) {
2147 new_start = event->note()->time() + front_delta;
2148 /* start moved toward the end, so move the end point back to where it used to be */
2149 new_length = event->note()->length() - front_delta;
2150 change_start = true;
2151 change_length = true;
2158 bool can_change = true;
2159 if (end_delta < 0) {
2160 if (event->note()->length() < -end_delta) {
2166 new_length = event->note()->length() + end_delta;
2167 change_length = true;
2172 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2175 if (change_length) {
2176 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2181 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2183 Evoral::MusicalTime new_time;
2187 if (event->note()->time() < -delta) {
2190 new_time = event->note()->time() + delta;
2193 new_time = event->note()->time() + delta;
2199 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2203 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2207 if (_selection.empty()) {
2222 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2223 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2229 start_diff_command(_("change velocities"));
2231 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2232 Selection::iterator next = i;
2234 change_note_velocity (*i, delta, true);
2243 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2245 if (_selection.empty()) {
2262 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2264 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2268 if ((int8_t) (*i)->note()->note() + delta > 127) {
2275 start_diff_command (_("transpose"));
2277 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2278 Selection::iterator next = i;
2280 change_note_note (*i, delta, true);
2288 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2290 Evoral::MusicalTime delta;
2295 /* grab the current grid distance */
2297 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2299 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2300 cerr << "Grid type not available as beats - TO BE FIXED\n";
2309 start_diff_command (_("change note lengths"));
2311 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2312 Selection::iterator next = i;
2315 /* note the negation of the delta for start */
2317 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2326 MidiRegionView::nudge_notes (bool forward)
2328 if (_selection.empty()) {
2332 /* pick a note as the point along the timeline to get the nudge distance.
2333 its not necessarily the earliest note, so we may want to pull the notes out
2334 into a vector and sort before using the first one.
2337 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2339 nframes64_t distance;
2341 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2343 /* grid is off - use nudge distance */
2345 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2351 nframes64_t next_pos = ref_point;
2354 /* XXX need check on max_frames, but that needs max_frames64 or something */
2357 if (next_pos == 0) {
2363 cerr << "ref point was " << ref_point << " next was " << next_pos;
2364 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2365 distance = ref_point - next_pos;
2366 cerr << " final is " << next_pos << " distance = " << distance << endl;
2369 if (distance == 0) {
2373 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2379 start_diff_command (_("nudge"));
2381 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2382 Selection::iterator next = i;
2384 change_note_time (*i, delta, true);
2392 MidiRegionView::change_channel(uint8_t channel)
2394 start_diff_command(_("change channel"));
2395 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2396 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2403 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2405 if (_mouse_state == SelectTouchDragging) {
2406 note_selected(ev, true);
2410 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (ev->note()->note()).c_str(), (int) ev->note()->note());
2411 PublicEditor& editor (trackview.editor());
2412 editor.show_verbose_canvas_cursor_with (buf);
2416 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2418 PublicEditor& editor (trackview.editor());
2419 editor.hide_verbose_canvas_cursor ();
2424 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2426 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2428 display_model(msrc->model());
2432 MidiRegionView::set_frame_color()
2435 if (_selected && should_show_selection) {
2436 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2438 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2444 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2448 case FilterChannels:
2449 _force_channel = -1;
2452 _force_channel = mask;
2453 mask = 0xFFFF; // Show all notes as active (below)
2456 // Update notes for selection
2457 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2458 (*i)->on_channel_selection_change(mask);
2461 _last_channel_selection = mask;
2465 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2467 _model_name = model;
2468 _custom_device_mode = custom_device_mode;
2473 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2475 if (_selection.empty()) {
2479 PublicEditor& editor (trackview.editor());
2484 editor.get_cut_buffer().add (selection_as_cut_buffer());
2492 start_delta_command();
2494 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2500 delta_remove_note (*i);
2510 MidiRegionView::selection_as_cut_buffer () const
2514 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2515 NoteType* n = (*i)->note().get();
2516 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2519 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2526 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2532 start_delta_command (_("paste"));
2534 Evoral::MusicalTime beat_delta;
2535 Evoral::MusicalTime paste_pos_beats;
2536 Evoral::MusicalTime duration;
2537 Evoral::MusicalTime end_point;
2539 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2540 paste_pos_beats = frames_to_beats (pos - _region->position());
2541 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2542 paste_pos_beats = 0;
2544 _selection.clear ();
2546 for (int n = 0; n < (int) times; ++n) {
2548 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2550 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2552 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2553 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2555 /* make all newly added notes selected */
2557 delta_add_note (copied_note, true);
2558 end_point = copied_note->end_time();
2561 paste_pos_beats += duration;
2564 /* if we pasted past the current end of the region, extend the region */
2566 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2567 nframes64_t region_end = _region->position() + _region->length() - 1;
2569 if (end_frame > region_end) {
2571 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2573 trackview.session()->begin_reversible_command (_("paste"));
2575 _region->clear_history ();
2576 _region->set_length (end_frame, this);
2577 trackview.session()->add_command (new StatefulDiffCommand (_region));
2580 cerr << "region end finally at " << _region->position() + _region->length() - 1;
2584 struct EventNoteTimeEarlyFirstComparator {
2585 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2586 return a->note()->time() < b->note()->time();
2591 MidiRegionView::time_sort_events ()
2593 if (!_sort_needed) {
2597 EventNoteTimeEarlyFirstComparator cmp;
2600 _sort_needed = false;
2604 MidiRegionView::goto_next_note ()
2606 // nframes64_t pos = -1;
2607 bool use_next = false;
2609 if (_events.back()->selected()) {
2613 time_sort_events ();
2615 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2616 if ((*i)->selected()) {
2619 } else if (use_next) {
2621 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2626 /* use the first one */
2628 unique_select (_events.front());
2633 MidiRegionView::goto_previous_note ()
2635 // nframes64_t pos = -1;
2636 bool use_next = false;
2638 if (_events.front()->selected()) {
2642 time_sort_events ();
2644 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2645 if ((*i)->selected()) {
2648 } else if (use_next) {
2650 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2655 /* use the last one */
2657 unique_select (*(_events.rbegin()));
2661 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2663 bool had_selected = false;
2665 time_sort_events ();
2667 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2668 if ((*i)->selected()) {
2669 selected.insert ((*i)->note());
2670 had_selected = true;
2674 if (allow_all_if_none_selected && !had_selected) {
2675 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2676 selected.insert ((*i)->note());