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 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
351 _pressed_button = ev->button.button;
352 _mouse_state = Pressed;
355 _pressed_button = ev->button.button;
358 case GDK_2BUTTON_PRESS:
361 case GDK_ENTER_NOTIFY:
362 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
363 Keyboard::magic_widget_grab_focus();
367 case GDK_MOTION_NOTIFY:
368 event_x = ev->motion.x;
369 event_y = ev->motion.y;
370 group->w2i(event_x, event_y);
372 // convert event_x to global frame
373 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
374 trackview.editor().snap_to(event_frame);
375 // convert event_frame back to local coordinates relative to position
376 event_frame -= _region->position();
378 switch (_mouse_state) {
379 case Pressed: // Drag start
382 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
383 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
384 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
387 drag_start_x = event_x;
388 drag_start_y = event_y;
390 drag_rect = new ArdourCanvas::SimpleRect(*group);
391 drag_rect->property_x1() = event_x;
392 drag_rect->property_y1() = event_y;
393 drag_rect->property_x2() = event_x;
394 drag_rect->property_y2() = event_y;
395 drag_rect->property_outline_what() = 0xFF;
396 drag_rect->property_outline_color_rgba()
397 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
398 drag_rect->property_fill_color_rgba()
399 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
401 _mouse_state = SelectRectDragging;
404 // Add note drag start
405 } else if (editor.current_mouse_mode() == MouseRange) {
406 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
407 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
410 drag_start_x = event_x;
411 drag_start_y = event_y;
413 drag_rect = new ArdourCanvas::SimpleRect(*group);
414 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
416 drag_rect->property_y1() = midi_stream_view()->note_to_y(
417 midi_stream_view()->y_to_note(event_y));
418 drag_rect->property_x2() = event_x;
419 drag_rect->property_y2() = drag_rect->property_y1()
420 + floor(midi_stream_view()->note_height());
421 drag_rect->property_outline_what() = 0xFF;
422 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
423 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
425 _mouse_state = AddDragging;
431 case SelectRectDragging: // Select drag motion
432 case AddDragging: // Add note drag motion
433 if (ev->motion.is_hint) {
436 GdkModifierType state;
437 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
442 if (_mouse_state == AddDragging)
443 event_x = trackview.editor().frame_to_pixel(event_frame);
446 if (event_x > drag_start_x)
447 drag_rect->property_x2() = event_x;
449 drag_rect->property_x1() = event_x;
452 if (drag_rect && _mouse_state == SelectRectDragging) {
453 if (event_y > drag_start_y)
454 drag_rect->property_y2() = event_y;
456 drag_rect->property_y1() = event_y;
458 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
464 case SelectTouchDragging:
472 case GDK_BUTTON_RELEASE:
473 event_x = ev->motion.x;
474 event_y = ev->motion.y;
475 group->w2i(event_x, event_y);
476 group->ungrab(ev->button.time);
477 event_frame = trackview.editor().pixel_to_frame(event_x);
479 if (ev->button.button == 3) {
481 } else if (_pressed_button != 1) {
485 switch (_mouse_state) {
486 case Pressed: // Clicked
487 switch (editor.current_mouse_mode()) {
495 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
500 create_note_at (event_x, event_y, beats);
508 case SelectRectDragging: // Select drag done
513 case AddDragging: // Add drag done
515 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
516 const double x = drag_rect->property_x1();
517 const double length = trackview.editor().pixel_to_frame(
518 drag_rect->property_x2() - drag_rect->property_x1());
520 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
535 MidiRegionView::show_list_editor ()
538 _list_editor = new MidiListEditor (trackview.session(), midi_region());
540 _list_editor->present ();
543 /** Add a note to the model, and the view, at a canvas (click) coordinate.
544 * \param x horizontal position in pixels
545 * \param y vertical position in pixels
546 * \param length duration of the note in beats */
548 MidiRegionView::create_note_at(double x, double y, double length)
550 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
551 MidiStreamView* const view = mtv->midi_view();
553 double note = midi_stream_view()->y_to_note(y);
556 assert(note <= 127.0);
558 // Start of note in frames relative to region start
559 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
560 assert(start_frames >= 0);
563 length = frames_to_beats(
564 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
566 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
567 frames_to_beats(start_frames + _region->start()), length,
568 (uint8_t)note, 0x40));
570 if (_model->contains (new_note)) {
574 view->update_note_range(new_note->note());
576 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
578 _model->apply_command(*trackview.session(), cmd);
580 play_midi_note (new_note);
584 MidiRegionView::clear_events()
589 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
590 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
595 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
600 _pgm_changes.clear();
602 _optimization_iterator = _events.end();
607 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
610 content_connection.disconnect ();
611 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
615 if (_enable_display) {
622 MidiRegionView::start_delta_command(string name)
624 if (!_delta_command) {
625 _delta_command = _model->new_delta_command(name);
630 MidiRegionView::start_diff_command(string name)
632 if (!_diff_command) {
633 _diff_command = _model->new_diff_command(name);
638 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
640 if (_delta_command) {
641 _delta_command->add(note);
644 _marked_for_selection.insert(note);
647 _marked_for_velocity.insert(note);
652 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
654 if (_delta_command && ev->note()) {
655 _delta_command->remove(ev->note());
660 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
661 MidiModel::DiffCommand::Property property,
665 _diff_command->change (ev->note(), property, val);
670 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
671 MidiModel::DiffCommand::Property property,
672 Evoral::MusicalTime val)
675 _diff_command->change (ev->note(), property, val);
680 MidiRegionView::apply_delta()
682 if (!_delta_command) {
686 // Mark all selected notes for selection when model reloads
687 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
688 _marked_for_selection.insert((*i)->note());
691 _model->apply_command(*trackview.session(), _delta_command);
693 midi_view()->midi_track()->playlist_modified();
695 _marked_for_selection.clear();
696 _marked_for_velocity.clear();
700 MidiRegionView::apply_diff ()
702 if (!_diff_command) {
706 _model->apply_command(*trackview.session(), _diff_command);
708 midi_view()->midi_track()->playlist_modified();
710 _marked_for_velocity.clear();
714 MidiRegionView::apply_delta_as_subcommand()
716 if (!_delta_command) {
720 // Mark all selected notes for selection when model reloads
721 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
722 _marked_for_selection.insert((*i)->note());
725 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
727 midi_view()->midi_track()->playlist_modified();
729 _marked_for_selection.clear();
730 _marked_for_velocity.clear();
734 MidiRegionView::apply_diff_as_subcommand()
736 if (!_diff_command) {
740 // Mark all selected notes for selection when model reloads
741 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
742 _marked_for_selection.insert((*i)->note());
745 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
747 midi_view()->midi_track()->playlist_modified();
749 _marked_for_selection.clear();
750 _marked_for_velocity.clear();
754 MidiRegionView::abort_command()
756 delete _delta_command;
758 delete _diff_command;
764 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
766 if (_optimization_iterator != _events.end()) {
767 ++_optimization_iterator;
770 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
771 return *_optimization_iterator;
774 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
775 if ((*_optimization_iterator)->note() == note) {
776 return *_optimization_iterator;
784 MidiRegionView::redisplay_model()
786 // Don't redisplay the model if we're currently recording and displaying that
792 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
796 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
800 MidiModel::ReadLock lock(_model->read_lock());
802 MidiModel::Notes& notes (_model->notes());
803 _optimization_iterator = _events.begin();
805 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
807 boost::shared_ptr<NoteType> note (*n);
808 CanvasNoteEvent* cne;
811 if (note_in_region_range (note, visible)) {
813 if ((cne = find_canvas_note (note)) != 0) {
820 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
822 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
834 add_note (note, visible);
839 if ((cne = find_canvas_note (note)) != 0) {
847 /* remove note items that are no longer valid */
849 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
850 if (!(*i)->valid ()) {
852 i = _events.erase (i);
859 display_program_changes();
861 _marked_for_selection.clear ();
862 _marked_for_velocity.clear ();
864 /* we may have caused _events to contain things out of order (e.g. if a note
865 moved earlier or later). we don't generally need them in time order, but
866 make a note that a sort is required for those cases that require it.
873 MidiRegionView::display_program_changes()
875 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
880 Glib::Mutex::Lock lock (control->list()->lock());
882 uint8_t channel = control->parameter().channel();
884 for (AutomationList::const_iterator event = control->list()->begin();
885 event != control->list()->end(); ++event) {
886 double event_time = (*event)->when;
887 double program_number = floor((*event)->value + 0.5);
889 // Get current value of bank select MSB at time of the program change
890 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
891 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
893 if (msb_control != 0) {
894 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
897 // Get current value of bank select LSB at time of the program change
898 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
899 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
901 if (lsb_control != 0) {
902 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
905 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
907 boost::shared_ptr<MIDI::Name::Patch> patch =
908 MIDI::Name::MidiPatchManager::instance().find_patch(
909 _model_name, _custom_device_mode, channel, patch_key);
911 PCEvent program_change(event_time, uint8_t(program_number), channel);
914 add_pgm_change(program_change, patch->name());
917 snprintf(buf, 4, "%d", int(program_number));
918 add_pgm_change(program_change, buf);
924 MidiRegionView::display_sysexes()
926 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
927 Evoral::MusicalTime time = (*i)->time();
932 for (uint32_t b = 0; b < (*i)->size(); ++b) {
933 str << int((*i)->buffer()[b]);
934 if (b != (*i)->size() -1) {
938 string text = str.str();
940 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
942 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
944 double height = midi_stream_view()->contents_height();
946 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
947 new CanvasSysEx(*this, *group, text, height, x, 1.0));
949 // Show unless program change is beyond the region bounds
950 if (time - _region->start() >= _region->length() || time < _region->start()) {
956 _sys_exes.push_back(sysex);
961 MidiRegionView::~MidiRegionView ()
963 in_destructor = true;
965 note_delete_connection.disconnect ();
969 RegionViewGoingAway (this); /* EMIT_SIGNAL */
978 delete _delta_command;
982 MidiRegionView::region_resized (const PropertyChange& what_changed)
984 RegionView::region_resized(what_changed);
986 if (what_changed.contains (ARDOUR::Properties::position)) {
987 set_duration(_region->length(), 0);
988 if (_enable_display) {
995 MidiRegionView::reset_width_dependent_items (double pixel_width)
997 RegionView::reset_width_dependent_items(pixel_width);
998 assert(_pixel_width == pixel_width);
1000 if (_enable_display) {
1006 MidiRegionView::set_height (double height)
1008 static const double FUDGE = 2.0;
1009 const double old_height = _height;
1010 RegionView::set_height(height);
1011 _height = height - FUDGE;
1013 apply_note_range(midi_stream_view()->lowest_note(),
1014 midi_stream_view()->highest_note(),
1015 height != old_height + FUDGE);
1018 name_pixbuf->raise_to_top();
1023 /** Apply the current note range from the stream view
1024 * by repositioning/hiding notes as necessary
1027 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1029 if (!_enable_display) {
1033 if (!force && _current_range_min == min && _current_range_max == max) {
1037 _current_range_min = min;
1038 _current_range_max = max;
1040 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1041 CanvasNoteEvent* event = *i;
1042 boost::shared_ptr<NoteType> note (event->note());
1044 if (note->note() < _current_range_min ||
1045 note->note() > _current_range_max) {
1051 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1053 const double y1 = midi_stream_view()->note_to_y(note->note());
1054 const double y2 = y1 + floor(midi_stream_view()->note_height());
1056 cnote->property_y1() = y1;
1057 cnote->property_y2() = y2;
1059 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1061 double x = trackview.editor().frame_to_pixel(
1062 beats_to_frames(note->time()) - _region->start());
1063 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1064 double y = midi_stream_view()->note_to_y(event->note()->note())
1065 + ((diamond_size-2.0) / 4.0);
1067 chit->set_height (diamond_size);
1068 chit->move (x - chit->x1(), y - chit->y1());
1075 MidiRegionView::add_ghost (TimeAxisView& tv)
1079 double unit_position = _region->position () / samples_per_unit;
1080 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1081 MidiGhostRegion* ghost;
1083 if (mtv && mtv->midi_view()) {
1084 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1085 to allow having midi notes on top of note lines and waveforms.
1087 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1089 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1092 ghost->set_height ();
1093 ghost->set_duration (_region->length() / samples_per_unit);
1094 ghosts.push_back (ghost);
1096 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1097 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1098 ghost->add_note(note);
1102 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1108 /** Begin tracking note state for successive calls to add_event
1111 MidiRegionView::begin_write()
1113 assert(!_active_notes);
1114 _active_notes = new CanvasNote*[128];
1115 for (unsigned i=0; i < 128; ++i) {
1116 _active_notes[i] = 0;
1121 /** Destroy note state for add_event
1124 MidiRegionView::end_write()
1126 delete[] _active_notes;
1128 _marked_for_selection.clear();
1129 _marked_for_velocity.clear();
1133 /** Resolve an active MIDI note (while recording).
1136 MidiRegionView::resolve_note(uint8_t note, double end_time)
1138 if (midi_view()->note_mode() != Sustained) {
1142 if (_active_notes && _active_notes[note]) {
1143 const nframes64_t end_time_frames = beats_to_frames(end_time);
1144 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1145 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1146 _active_notes[note] = 0;
1151 /** Extend active notes to rightmost edge of region (if length is changed)
1154 MidiRegionView::extend_active_notes()
1156 if (!_active_notes) {
1160 for (unsigned i=0; i < 128; ++i) {
1161 if (_active_notes[i]) {
1162 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1168 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1170 if (no_sound_notes || !trackview.editor().sound_notes()) {
1174 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1177 route_ui->midi_track()->write_immediate_event(
1178 note->on_event().size(), note->on_event().buffer());
1180 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1181 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1182 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1183 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1184 note_length_ms, G_PRIORITY_DEFAULT);
1188 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1190 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1193 route_ui->midi_track()->write_immediate_event(
1194 note->off_event().size(), note->off_event().buffer());
1200 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1202 const nframes64_t note_start_frames = beats_to_frames(note->time());
1204 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1205 (note_start_frames < _region->start());
1207 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1208 (note->note() <= midi_stream_view()->highest_note());
1214 MidiRegionView::update_note (CanvasNote* ev)
1216 boost::shared_ptr<NoteType> note = ev->note();
1218 const nframes64_t note_start_frames = beats_to_frames(note->time());
1219 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1221 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1222 const double y1 = midi_stream_view()->note_to_y(note->note());
1223 const double note_endpixel =
1224 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1226 ev->property_x1() = x;
1227 ev->property_y1() = y1;
1228 if (note->length() > 0) {
1229 ev->property_x2() = note_endpixel;
1231 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1233 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1235 if (note->length() == 0) {
1236 if (_active_notes) {
1237 assert(note->note() < 128);
1238 // If this note is already active there's a stuck note,
1239 // finish the old note rectangle
1240 if (_active_notes[note->note()]) {
1241 CanvasNote* const old_rect = _active_notes[note->note()];
1242 boost::shared_ptr<NoteType> old_note = old_rect->note();
1243 old_rect->property_x2() = x;
1244 old_rect->property_outline_what() = (guint32) 0xF;
1246 _active_notes[note->note()] = ev;
1248 /* outline all but right edge */
1249 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1251 /* outline all edges */
1252 ev->property_outline_what() = (guint32) 0xF;
1257 MidiRegionView::update_hit (CanvasHit* ev)
1259 boost::shared_ptr<NoteType> note = ev->note();
1261 const nframes64_t note_start_frames = beats_to_frames(note->time());
1262 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1263 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1264 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1269 /** Add a MIDI note to the view (with length).
1271 * If in sustained mode, notes with length 0 will be considered active
1272 * notes, and resolve_note should be called when the corresponding note off
1273 * event arrives, to properly display the note.
1276 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1278 CanvasNoteEvent* event = 0;
1280 assert(note->time() >= 0);
1281 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1283 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1285 if (midi_view()->note_mode() == Sustained) {
1287 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1289 update_note (ev_rect);
1293 MidiGhostRegion* gr;
1295 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1296 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1297 gr->add_note(ev_rect);
1301 } else if (midi_view()->note_mode() == Percussive) {
1303 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1305 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1307 update_hit (ev_diamond);
1316 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1317 note_selected(event, true);
1320 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1321 event->show_velocity();
1323 event->on_channel_selection_change(_last_channel_selection);
1324 _events.push_back(event);
1335 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1336 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1338 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1340 start_delta_command (_("step add"));
1341 delta_add_note (new_note, true, false);
1344 /* potentially extend region to hold new note */
1346 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1347 nframes64_t region_end = _region->position() + _region->length() - 1;
1349 if (end_frame > region_end) {
1350 _region->set_length (end_frame, this);
1357 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1359 assert(program.time >= 0);
1361 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1362 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1364 double height = midi_stream_view()->contents_height();
1366 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1367 new CanvasProgramChange(*this, *group,
1372 _custom_device_mode,
1373 program.time, program.channel, program.value));
1375 // Show unless program change is beyond the region bounds
1376 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1382 _pgm_changes.push_back(pgm_change);
1386 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1388 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1389 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1390 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1392 if (msb_control != 0) {
1393 msb = int(msb_control->get_float(true, time));
1394 cerr << "got msb " << msb;
1397 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1398 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1400 if (lsb_control != 0) {
1401 lsb = lsb_control->get_float(true, time);
1402 cerr << " got lsb " << lsb;
1405 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1406 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1407 float program_number = -1.0;
1408 if (program_control != 0) {
1409 program_number = program_control->get_float(true, time);
1410 cerr << " got program " << program_number << endl;
1413 key.msb = (int) floor(msb + 0.5);
1414 key.lsb = (int) floor(lsb + 0.5);
1415 key.program_number = (int) floor(program_number + 0.5);
1416 assert(key.is_sane());
1421 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1423 // TODO: Get the real event here and alter them at the original times
1424 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1425 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1426 if (msb_control != 0) {
1427 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1430 // TODO: Get the real event here and alter them at the original times
1431 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1432 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1433 if (lsb_control != 0) {
1434 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1437 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1438 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1440 assert(program_control != 0);
1441 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1447 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1449 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1450 alter_program_change(program_change_event, new_patch);
1454 MidiRegionView::previous_program(CanvasProgramChange& program)
1456 MIDI::Name::PatchPrimaryKey key;
1457 get_patch_key_at(program.event_time(), program.channel(), key);
1459 boost::shared_ptr<MIDI::Name::Patch> patch =
1460 MIDI::Name::MidiPatchManager::instance().previous_patch(
1462 _custom_device_mode,
1466 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1468 alter_program_change(program_change_event, patch->patch_primary_key());
1473 MidiRegionView::next_program(CanvasProgramChange& program)
1475 MIDI::Name::PatchPrimaryKey key;
1476 get_patch_key_at(program.event_time(), program.channel(), key);
1478 boost::shared_ptr<MIDI::Name::Patch> patch =
1479 MIDI::Name::MidiPatchManager::instance().next_patch(
1481 _custom_device_mode,
1485 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1487 alter_program_change(program_change_event, patch->patch_primary_key());
1492 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1494 if (_selection.empty()) {
1498 if (_selection.erase (cne) > 0) {
1499 cerr << "Erased a CNE from selection\n";
1504 MidiRegionView::delete_selection()
1506 if (_selection.empty()) {
1510 start_delta_command (_("delete selection"));
1512 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1513 if ((*i)->selected()) {
1514 _delta_command->remove((*i)->note());
1524 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1526 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1527 if ((*i)->selected() && (*i) != ev) {
1528 (*i)->selected(false);
1529 (*i)->hide_velocity();
1537 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1539 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1542 Selection::iterator tmp = i;
1545 (*i)->selected (false);
1546 _selection.erase (i);
1555 /* don't bother with removing this regionview from the editor selection,
1556 since we're about to add another note, and thus put/keep this
1557 regionview in the editor selection.
1560 if (!ev->selected()) {
1561 add_to_selection (ev);
1566 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1568 uint8_t low_note = 127;
1569 uint8_t high_note = 0;
1570 MidiModel::Notes& notes (_model->notes());
1571 _optimization_iterator = _events.begin();
1573 if (extend && _selection.empty()) {
1579 /* scan existing selection to get note range */
1581 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1582 if ((*i)->note()->note() < low_note) {
1583 low_note = (*i)->note()->note();
1585 if ((*i)->note()->note() > high_note) {
1586 high_note = (*i)->note()->note();
1590 low_note = min (low_note, notenum);
1591 high_note = max (high_note, notenum);
1594 no_sound_notes = true;
1596 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1598 boost::shared_ptr<NoteType> note (*n);
1599 CanvasNoteEvent* cne;
1600 bool select = false;
1602 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1604 if ((note->note() >= low_note && note->note() <= high_note)) {
1607 } else if (note->note() == notenum) {
1613 if ((cne = find_canvas_note (note)) != 0) {
1614 // extend is false because we've taken care of it,
1615 // since it extends by time range, not pitch.
1616 note_selected (cne, add, false);
1620 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1624 no_sound_notes = false;
1628 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1630 MidiModel::Notes& notes (_model->notes());
1631 _optimization_iterator = _events.begin();
1633 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1635 boost::shared_ptr<NoteType> note (*n);
1636 CanvasNoteEvent* cne;
1638 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1639 if ((cne = find_canvas_note (note)) != 0) {
1640 if (cne->selected()) {
1641 note_deselected (cne);
1643 note_selected (cne, true, false);
1651 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1654 clear_selection_except(ev);
1659 if (!ev->selected()) {
1660 add_to_selection (ev);
1664 /* find end of latest note selected, select all between that and the start of "ev" */
1666 Evoral::MusicalTime earliest = DBL_MAX;
1667 Evoral::MusicalTime latest = 0;
1669 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1670 if ((*i)->note()->end_time() > latest) {
1671 latest = (*i)->note()->end_time();
1673 if ((*i)->note()->time() < earliest) {
1674 earliest = (*i)->note()->time();
1678 if (ev->note()->end_time() > latest) {
1679 latest = ev->note()->end_time();
1682 if (ev->note()->time() < earliest) {
1683 earliest = ev->note()->time();
1686 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1688 /* find notes entirely within OR spanning the earliest..latest range */
1690 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1691 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1692 add_to_selection (*i);
1696 /* if events were guaranteed to be time sorted, we could do this.
1697 but as of sept 10th 2009, they no longer are.
1700 if ((*i)->note()->time() > latest) {
1709 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1711 remove_from_selection (ev);
1715 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1725 // TODO: Make this faster by storing the last updated selection rect, and only
1726 // adjusting things that are in the area that appears/disappeared.
1727 // We probably need a tree to be able to find events in O(log(n)) time.
1729 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1731 /* check if any corner of the note is inside the rect
1734 1) this is computing "touched by", not "contained by" the rect.
1735 2) this does not require that events be sorted in time.
1738 const double ix1 = (*i)->x1();
1739 const double ix2 = (*i)->x2();
1740 const double iy1 = (*i)->y1();
1741 const double iy2 = (*i)->y2();
1743 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1744 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1745 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1746 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1749 if (!(*i)->selected()) {
1750 add_to_selection (*i);
1752 } else if ((*i)->selected()) {
1753 // Not inside rectangle
1754 remove_from_selection (*i);
1760 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1762 Selection::iterator i = _selection.find (ev);
1764 if (i != _selection.end()) {
1765 _selection.erase (i);
1768 ev->selected (false);
1769 ev->hide_velocity ();
1771 if (_selection.empty()) {
1772 PublicEditor& editor (trackview.editor());
1773 editor.get_selection().remove (this);
1778 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1780 bool add_mrv_selection = false;
1782 if (_selection.empty()) {
1783 add_mrv_selection = true;
1786 if (_selection.insert (ev).second) {
1787 ev->selected (true);
1788 play_midi_note ((ev)->note());
1791 if (add_mrv_selection) {
1792 PublicEditor& editor (trackview.editor());
1793 editor.get_selection().add (this);
1798 MidiRegionView::move_selection(double dx, double dy)
1800 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1801 (*i)->move_event(dx, dy);
1806 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1808 assert (!_selection.empty());
1810 uint8_t lowest_note_in_selection = 127;
1811 uint8_t highest_note_in_selection = 0;
1812 uint8_t highest_note_difference = 0;
1814 // find highest and lowest notes first
1816 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1817 uint8_t pitch = (*i)->note()->note();
1818 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1819 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1823 cerr << "dnote: " << (int) dnote << endl;
1824 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1825 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1826 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1827 << int(highest_note_in_selection) << endl;
1828 cerr << "selection size: " << _selection.size() << endl;
1829 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1832 // Make sure the note pitch does not exceed the MIDI standard range
1833 if (highest_note_in_selection + dnote > 127) {
1834 highest_note_difference = highest_note_in_selection - 127;
1837 start_diff_command(_("move notes"));
1839 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1841 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1844 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1846 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1849 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1855 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1857 uint8_t original_pitch = (*i)->note()->note();
1858 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1860 // keep notes in standard midi range
1861 clamp_to_0_127(new_pitch);
1863 // keep original pitch if note is dragged outside valid midi range
1864 if ((original_pitch != 0 && new_pitch == 0)
1865 || (original_pitch != 127 && new_pitch == 127)) {
1866 new_pitch = original_pitch;
1869 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1870 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1872 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1877 // care about notes being moved beyond the upper/lower bounds on the canvas
1878 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1879 highest_note_in_selection > midi_stream_view()->highest_note()) {
1880 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1885 MidiRegionView::snap_pixel_to_frame(double x)
1887 PublicEditor& editor = trackview.editor();
1888 // x is region relative, convert it to global absolute frames
1889 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1890 editor.snap_to(frame);
1891 return frame - _region->position(); // convert back to region relative
1895 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1897 PublicEditor& editor = trackview.editor();
1898 // x is region relative, convert it to global absolute frames
1899 nframes64_t frame = x + _region->position();
1900 editor.snap_to(frame);
1901 return frame - _region->position(); // convert back to region relative
1905 MidiRegionView::snap_to_pixel(double x)
1907 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1911 MidiRegionView::get_position_pixels()
1913 nframes64_t region_frame = get_position();
1914 return trackview.editor().frame_to_pixel(region_frame);
1918 MidiRegionView::get_end_position_pixels()
1920 nframes64_t frame = get_position() + get_duration ();
1921 return trackview.editor().frame_to_pixel(frame);
1925 MidiRegionView::beats_to_frames(double beats) const
1927 return _time_converter.to(beats);
1931 MidiRegionView::frames_to_beats(nframes64_t frames) const
1933 return _time_converter.from(frames);
1937 MidiRegionView::begin_resizing (bool /*at_front*/)
1939 _resize_data.clear();
1941 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1942 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1944 // only insert CanvasNotes into the map
1946 NoteResizeData *resize_data = new NoteResizeData();
1947 resize_data->canvas_note = note;
1949 // create a new SimpleRect from the note which will be the resize preview
1950 SimpleRect *resize_rect = new SimpleRect(
1951 *group, note->x1(), note->y1(), note->x2(), note->y2());
1953 // calculate the colors: get the color settings
1954 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1955 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1958 // make the resize preview notes more transparent and bright
1959 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1961 // calculate color based on note velocity
1962 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1963 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1967 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1968 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1970 resize_data->resize_rect = resize_rect;
1971 _resize_data.push_back(resize_data);
1977 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1979 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1980 SimpleRect* resize_rect = (*i)->resize_rect;
1981 CanvasNote* canvas_note = (*i)->canvas_note;
1986 current_x = canvas_note->x1() + delta_x;
1988 // x is in track relative, transform it to region relative
1989 current_x = delta_x - get_position_pixels();
1993 current_x = canvas_note->x2() + delta_x;
1995 // x is in track relative, transform it to region relative
1996 current_x = delta_x - get_end_position_pixels ();
2001 resize_rect->property_x1() = snap_to_pixel(current_x);
2002 resize_rect->property_x2() = canvas_note->x2();
2004 resize_rect->property_x2() = snap_to_pixel(current_x);
2005 resize_rect->property_x1() = canvas_note->x1();
2011 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
2013 start_diff_command(_("resize notes"));
2015 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2016 CanvasNote* canvas_note = (*i)->canvas_note;
2017 SimpleRect* resize_rect = (*i)->resize_rect;
2018 const double region_start = get_position_pixels();
2023 current_x = canvas_note->x1() + delta_x;
2025 // x is in track relative, transform it to region relative
2026 current_x = region_start + delta_x;
2030 current_x = canvas_note->x2() + delta_x;
2032 // x is in track relative, transform it to region relative
2033 current_x = region_start + delta_x;
2037 current_x = snap_pixel_to_frame (current_x);
2038 current_x = frames_to_beats (current_x);
2040 if (at_front && current_x < canvas_note->note()->end_time()) {
2041 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2043 double len = canvas_note->note()->time() - current_x;
2044 len += canvas_note->note()->length();
2047 /* XXX convert to beats */
2048 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2053 double len = current_x - canvas_note->note()->time();
2056 /* XXX convert to beats */
2057 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2065 _resize_data.clear();
2070 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2072 uint8_t new_velocity;
2075 new_velocity = event->note()->velocity() + velocity;
2076 clamp_to_0_127(new_velocity);
2078 new_velocity = velocity;
2081 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2085 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2090 new_note = event->note()->note() + note;
2095 clamp_to_0_127 (new_note);
2096 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2100 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2102 bool change_start = false;
2103 bool change_length = false;
2104 Evoral::MusicalTime new_start;
2105 Evoral::MusicalTime new_length;
2107 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2109 front_delta: if positive - move the start of the note later in time (shortening it)
2110 if negative - move the start of the note earlier in time (lengthening it)
2112 end_delta: if positive - move the end of the note later in time (lengthening it)
2113 if negative - move the end of the note earlier in time (shortening it)
2117 if (front_delta < 0) {
2119 if (event->note()->time() < -front_delta) {
2122 new_start = event->note()->time() + front_delta; // moves earlier
2125 /* start moved toward zero, so move the end point out to where it used to be.
2126 Note that front_delta is negative, so this increases the length.
2129 new_length = event->note()->length() - front_delta;
2130 change_start = true;
2131 change_length = true;
2135 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2137 if (new_pos < event->note()->end_time()) {
2138 new_start = event->note()->time() + front_delta;
2139 /* start moved toward the end, so move the end point back to where it used to be */
2140 new_length = event->note()->length() - front_delta;
2141 change_start = true;
2142 change_length = true;
2149 bool can_change = true;
2150 if (end_delta < 0) {
2151 if (event->note()->length() < -end_delta) {
2157 new_length = event->note()->length() + end_delta;
2158 change_length = true;
2163 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2166 if (change_length) {
2167 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2172 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2174 Evoral::MusicalTime new_time;
2178 if (event->note()->time() < -delta) {
2181 new_time = event->note()->time() + delta;
2184 new_time = event->note()->time() + delta;
2190 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2194 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2198 if (_selection.empty()) {
2213 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2214 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2220 start_diff_command(_("change velocities"));
2222 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2223 Selection::iterator next = i;
2225 change_note_velocity (*i, delta, true);
2234 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2236 if (_selection.empty()) {
2253 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2255 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2259 if ((int8_t) (*i)->note()->note() + delta > 127) {
2266 start_diff_command (_("transpose"));
2268 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2269 Selection::iterator next = i;
2271 change_note_note (*i, delta, true);
2279 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2281 Evoral::MusicalTime delta;
2286 /* grab the current grid distance */
2288 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2290 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2291 cerr << "Grid type not available as beats - TO BE FIXED\n";
2300 start_diff_command (_("change note lengths"));
2302 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2303 Selection::iterator next = i;
2306 /* note the negation of the delta for start */
2308 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2317 MidiRegionView::nudge_notes (bool forward)
2319 if (_selection.empty()) {
2323 /* pick a note as the point along the timeline to get the nudge distance.
2324 its not necessarily the earliest note, so we may want to pull the notes out
2325 into a vector and sort before using the first one.
2328 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2330 nframes64_t distance;
2332 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2334 /* grid is off - use nudge distance */
2336 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2342 nframes64_t next_pos = ref_point;
2345 /* XXX need check on max_frames, but that needs max_frames64 or something */
2348 if (next_pos == 0) {
2354 cerr << "ref point was " << ref_point << " next was " << next_pos;
2355 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2356 distance = ref_point - next_pos;
2357 cerr << " final is " << next_pos << " distance = " << distance << endl;
2360 if (distance == 0) {
2364 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2370 start_diff_command (_("nudge"));
2372 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2373 Selection::iterator next = i;
2375 change_note_time (*i, delta, true);
2383 MidiRegionView::change_channel(uint8_t channel)
2385 start_diff_command(_("change channel"));
2386 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2387 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2394 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2396 if (_mouse_state == SelectTouchDragging) {
2397 note_selected(ev, true);
2401 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (ev->note()->note()).c_str(), (int) ev->note()->note());
2402 PublicEditor& editor (trackview.editor());
2403 editor.show_verbose_canvas_cursor_with (buf);
2407 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2409 PublicEditor& editor (trackview.editor());
2410 editor.hide_verbose_canvas_cursor ();
2415 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2417 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2419 display_model(msrc->model());
2423 MidiRegionView::set_frame_color()
2426 if (_selected && should_show_selection) {
2427 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2429 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2435 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2439 case FilterChannels:
2440 _force_channel = -1;
2443 _force_channel = mask;
2444 mask = 0xFFFF; // Show all notes as active (below)
2447 // Update notes for selection
2448 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2449 (*i)->on_channel_selection_change(mask);
2452 _last_channel_selection = mask;
2456 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2458 _model_name = model;
2459 _custom_device_mode = custom_device_mode;
2464 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2466 if (_selection.empty()) {
2470 PublicEditor& editor (trackview.editor());
2475 editor.get_cut_buffer().add (selection_as_cut_buffer());
2483 start_delta_command();
2485 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2491 delta_remove_note (*i);
2501 MidiRegionView::selection_as_cut_buffer () const
2505 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2506 NoteType* n = (*i)->note().get();
2507 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2510 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2517 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2523 start_delta_command (_("paste"));
2525 Evoral::MusicalTime beat_delta;
2526 Evoral::MusicalTime paste_pos_beats;
2527 Evoral::MusicalTime duration;
2528 Evoral::MusicalTime end_point;
2530 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2531 paste_pos_beats = frames_to_beats (pos - _region->position());
2532 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2533 paste_pos_beats = 0;
2535 _selection.clear ();
2537 for (int n = 0; n < (int) times; ++n) {
2539 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2541 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2543 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2544 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2546 /* make all newly added notes selected */
2548 delta_add_note (copied_note, true);
2549 end_point = copied_note->end_time();
2552 paste_pos_beats += duration;
2555 /* if we pasted past the current end of the region, extend the region */
2557 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2558 nframes64_t region_end = _region->position() + _region->length() - 1;
2560 if (end_frame > region_end) {
2562 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2564 trackview.session()->begin_reversible_command (_("paste"));
2566 _region->clear_history ();
2567 _region->set_length (end_frame, this);
2568 trackview.session()->add_command (new StatefulDiffCommand (_region));
2571 cerr << "region end finally at " << _region->position() + _region->length() - 1;
2575 struct EventNoteTimeEarlyFirstComparator {
2576 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2577 return a->note()->time() < b->note()->time();
2582 MidiRegionView::time_sort_events ()
2584 if (!_sort_needed) {
2588 EventNoteTimeEarlyFirstComparator cmp;
2591 _sort_needed = false;
2595 MidiRegionView::goto_next_note ()
2597 // nframes64_t pos = -1;
2598 bool use_next = false;
2600 if (_events.back()->selected()) {
2604 time_sort_events ();
2606 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2607 if ((*i)->selected()) {
2610 } else if (use_next) {
2612 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2617 /* use the first one */
2619 unique_select (_events.front());
2624 MidiRegionView::goto_previous_note ()
2626 // nframes64_t pos = -1;
2627 bool use_next = false;
2629 if (_events.front()->selected()) {
2633 time_sort_events ();
2635 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2636 if ((*i)->selected()) {
2639 } else if (use_next) {
2641 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2646 /* use the last one */
2648 unique_select (*(_events.rbegin()));
2652 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2654 bool had_selected = false;
2656 time_sort_events ();
2658 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2659 if ((*i)->selected()) {
2660 selected.insert ((*i)->note());
2661 had_selected = true;
2665 if (allow_all_if_none_selected && !had_selected) {
2666 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2667 selected.insert ((*i)->note());