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"
45 #include "automation_region_view.h"
46 #include "automation_time_axis.h"
47 #include "canvas-hit.h"
48 #include "canvas-note.h"
49 #include "canvas-program-change.h"
50 #include "ghostregion.h"
51 #include "gui_thread.h"
53 #include "midi_cut_buffer.h"
54 #include "midi_list_editor.h"
55 #include "midi_region_view.h"
56 #include "midi_streamview.h"
57 #include "midi_time_axis.h"
58 #include "midi_time_axis.h"
59 #include "midi_util.h"
60 #include "public_editor.h"
61 #include "selection.h"
62 #include "simpleline.h"
63 #include "streamview.h"
68 using namespace ARDOUR;
70 using namespace Editing;
71 using namespace ArdourCanvas;
72 using Gtkmm2ext::Keyboard;
74 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
75 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
76 : RegionView (parent, tv, r, spu, basic_color)
78 , _last_channel_selection(0xFFFF)
79 , _current_range_min(0)
80 , _current_range_max(0)
81 , _model_name(string())
82 , _custom_device_mode(string())
84 , _note_group(new ArdourCanvas::Group(*parent))
90 , _optimization_iterator (_events.end())
92 , no_sound_notes (false)
94 _note_group->raise_to_top();
97 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
98 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
99 TimeAxisViewItem::Visibility visibility)
100 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
102 , _last_channel_selection(0xFFFF)
103 , _model_name(string())
104 , _custom_device_mode(string())
106 , _note_group(new ArdourCanvas::Group(*parent))
111 , _sort_needed (true)
112 , _optimization_iterator (_events.end())
114 , no_sound_notes (false)
117 _note_group->raise_to_top();
121 MidiRegionView::MidiRegionView (const MidiRegionView& other)
122 : sigc::trackable(other)
125 , _last_channel_selection(0xFFFF)
126 , _model_name(string())
127 , _custom_device_mode(string())
129 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
134 , _sort_needed (true)
135 , _optimization_iterator (_events.end())
137 , no_sound_notes (false)
142 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
143 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
148 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
149 : RegionView (other, boost::shared_ptr<Region> (region))
151 , _last_channel_selection(0xFFFF)
152 , _model_name(string())
153 , _custom_device_mode(string())
155 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
160 , _sort_needed (true)
161 , _optimization_iterator (_events.end())
163 , no_sound_notes (false)
168 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
169 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
175 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
177 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
178 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
182 midi_region()->midi_source(0)->load_model();
185 _model = midi_region()->midi_source(0)->model();
186 _enable_display = false;
188 RegionView::init (basic_color, false);
190 compute_colors (basic_color);
192 set_height (trackview.current_height());
195 region_sync_changed ();
196 region_resized (ARDOUR::bounds_change);
199 reset_width_dependent_items (_pixel_width);
203 _enable_display = true;
206 display_model (_model);
210 group->raise_to_top();
211 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
213 midi_view()->signal_channel_mode_changed().connect(
214 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
216 midi_view()->signal_midi_patch_settings_changed().connect(
217 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
221 MidiRegionView::canvas_event(GdkEvent* ev)
223 PublicEditor& editor (trackview.editor());
225 if (!editor.internal_editing()) {
229 static double drag_start_x, drag_start_y;
230 static double last_x, last_y;
231 double event_x, event_y;
232 nframes64_t event_frame = 0;
235 static ArdourCanvas::SimpleRect* drag_rect = 0;
237 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
238 to its items, which means that ev->type == GDK_SCROLL will never be seen
243 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
245 if (ev->scroll.direction == GDK_SCROLL_UP) {
246 change_velocities (true, fine, false);
248 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
249 change_velocities (false, fine, false);
258 /* since GTK bindings are generally activated on press, and since
259 detectable auto-repeat is the name of the game and only sends
260 repeated presses, carry out key actions at key press, not release.
263 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
264 _mouse_state = SelectTouchDragging;
267 } else if (ev->key.keyval == GDK_Escape) {
271 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
273 bool start = (ev->key.keyval == GDK_comma);
274 bool end = (ev->key.keyval == GDK_period);
275 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
276 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
278 change_note_lengths (fine, shorter, start, end);
282 } else if (ev->key.keyval == GDK_Delete) {
287 } else if (ev->key.keyval == GDK_Tab) {
289 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
290 goto_previous_note ();
296 } else if (ev->key.keyval == GDK_Up) {
298 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
299 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
301 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
302 change_velocities (true, fine, allow_smush);
304 transpose (true, fine, allow_smush);
308 } else if (ev->key.keyval == GDK_Down) {
310 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
311 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
313 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
314 change_velocities (false, fine, allow_smush);
316 transpose (false, fine, allow_smush);
320 } else if (ev->key.keyval == GDK_Left) {
325 } else if (ev->key.keyval == GDK_Right) {
330 } else if (ev->key.keyval == GDK_Control_L) {
333 } else if (ev->key.keyval == GDK_r) {
334 /* if we're not step editing, this really doesn't matter */
335 midi_view()->step_edit_rest ();
341 case GDK_KEY_RELEASE:
342 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
348 case GDK_BUTTON_PRESS:
349 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
350 _pressed_button = ev->button.button;
351 _mouse_state = Pressed;
354 _pressed_button = ev->button.button;
357 case GDK_2BUTTON_PRESS:
360 case GDK_ENTER_NOTIFY:
361 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
362 Keyboard::magic_widget_grab_focus();
366 case GDK_MOTION_NOTIFY:
367 event_x = ev->motion.x;
368 event_y = ev->motion.y;
369 group->w2i(event_x, event_y);
371 // convert event_x to global frame
372 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
373 trackview.editor().snap_to(event_frame);
374 // convert event_frame back to local coordinates relative to position
375 event_frame -= _region->position();
377 switch (_mouse_state) {
378 case Pressed: // Drag start
381 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
382 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
383 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
386 drag_start_x = event_x;
387 drag_start_y = event_y;
389 drag_rect = new ArdourCanvas::SimpleRect(*group);
390 drag_rect->property_x1() = event_x;
391 drag_rect->property_y1() = event_y;
392 drag_rect->property_x2() = event_x;
393 drag_rect->property_y2() = event_y;
394 drag_rect->property_outline_what() = 0xFF;
395 drag_rect->property_outline_color_rgba()
396 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
397 drag_rect->property_fill_color_rgba()
398 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
400 _mouse_state = SelectRectDragging;
403 // Add note drag start
404 } else if (editor.current_mouse_mode() == MouseRange) {
405 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
406 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
409 drag_start_x = event_x;
410 drag_start_y = event_y;
412 drag_rect = new ArdourCanvas::SimpleRect(*group);
413 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
415 drag_rect->property_y1() = midi_stream_view()->note_to_y(
416 midi_stream_view()->y_to_note(event_y));
417 drag_rect->property_x2() = event_x;
418 drag_rect->property_y2() = drag_rect->property_y1()
419 + floor(midi_stream_view()->note_height());
420 drag_rect->property_outline_what() = 0xFF;
421 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
422 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
424 _mouse_state = AddDragging;
430 case SelectRectDragging: // Select drag motion
431 case AddDragging: // Add note drag motion
432 if (ev->motion.is_hint) {
435 GdkModifierType state;
436 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
441 if (_mouse_state == AddDragging)
442 event_x = trackview.editor().frame_to_pixel(event_frame);
445 if (event_x > drag_start_x)
446 drag_rect->property_x2() = event_x;
448 drag_rect->property_x1() = event_x;
451 if (drag_rect && _mouse_state == SelectRectDragging) {
452 if (event_y > drag_start_y)
453 drag_rect->property_y2() = event_y;
455 drag_rect->property_y1() = event_y;
457 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
463 case SelectTouchDragging:
471 case GDK_BUTTON_RELEASE:
472 event_x = ev->motion.x;
473 event_y = ev->motion.y;
474 group->w2i(event_x, event_y);
475 group->ungrab(ev->button.time);
476 event_frame = trackview.editor().pixel_to_frame(event_x);
478 if (ev->button.button == 3) {
480 } else if (_pressed_button != 1) {
484 switch (_mouse_state) {
485 case Pressed: // Clicked
486 switch (editor.current_mouse_mode()) {
494 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
499 create_note_at (event_x, event_y, beats);
507 case SelectRectDragging: // Select drag done
512 case AddDragging: // Add drag done
514 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
515 const double x = drag_rect->property_x1();
516 const double length = trackview.editor().pixel_to_frame(
517 drag_rect->property_x2() - drag_rect->property_x1());
519 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
534 MidiRegionView::show_list_editor ()
537 _list_editor = new MidiListEditor (trackview.session(), midi_region());
539 _list_editor->present ();
542 /** Add a note to the model, and the view, at a canvas (click) coordinate.
543 * \param x horizontal position in pixels
544 * \param y vertical position in pixels
545 * \param length duration of the note in beats */
547 MidiRegionView::create_note_at(double x, double y, double length)
549 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
550 MidiStreamView* const view = mtv->midi_view();
552 double note = midi_stream_view()->y_to_note(y);
555 assert(note <= 127.0);
557 // Start of note in frames relative to region start
558 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
559 assert(start_frames >= 0);
562 length = frames_to_beats(
563 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
565 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
566 frames_to_beats(start_frames + _region->start()), length,
567 (uint8_t)note, 0x40));
569 view->update_note_range(new_note->note());
571 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
573 _model->apply_command(*trackview.session(), cmd);
575 play_midi_note (new_note);
579 MidiRegionView::clear_events()
584 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
585 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
590 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
595 _pgm_changes.clear();
597 _optimization_iterator = _events.end();
602 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
605 content_connection.disconnect ();
606 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
610 if (_enable_display) {
617 MidiRegionView::start_delta_command(string name)
619 if (!_delta_command) {
620 _delta_command = _model->new_delta_command(name);
625 MidiRegionView::start_diff_command(string name)
627 if (!_diff_command) {
628 _diff_command = _model->new_diff_command(name);
633 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
635 if (_delta_command) {
636 _delta_command->add(note);
639 _marked_for_selection.insert(note);
642 _marked_for_velocity.insert(note);
647 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
649 if (_delta_command && ev->note()) {
650 _delta_command->remove(ev->note());
655 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
656 MidiModel::DiffCommand::Property property,
660 _diff_command->change (ev->note(), property, val);
665 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
666 MidiModel::DiffCommand::Property property,
667 Evoral::MusicalTime val)
670 _diff_command->change (ev->note(), property, val);
675 MidiRegionView::apply_delta()
677 if (!_delta_command) {
681 // Mark all selected notes for selection when model reloads
682 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
683 _marked_for_selection.insert((*i)->note());
686 _model->apply_command(*trackview.session(), _delta_command);
688 midi_view()->midi_track()->playlist_modified();
690 _marked_for_selection.clear();
691 _marked_for_velocity.clear();
695 MidiRegionView::apply_diff ()
697 if (!_diff_command) {
701 _model->apply_command(*trackview.session(), _diff_command);
703 midi_view()->midi_track()->playlist_modified();
705 _marked_for_velocity.clear();
709 MidiRegionView::apply_delta_as_subcommand()
711 if (!_delta_command) {
715 // Mark all selected notes for selection when model reloads
716 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
717 _marked_for_selection.insert((*i)->note());
720 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
722 midi_view()->midi_track()->playlist_modified();
724 _marked_for_selection.clear();
725 _marked_for_velocity.clear();
729 MidiRegionView::apply_diff_as_subcommand()
731 if (!_diff_command) {
735 // Mark all selected notes for selection when model reloads
736 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
737 _marked_for_selection.insert((*i)->note());
740 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
742 midi_view()->midi_track()->playlist_modified();
744 _marked_for_selection.clear();
745 _marked_for_velocity.clear();
749 MidiRegionView::abort_command()
751 delete _delta_command;
753 delete _diff_command;
759 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
761 if (_optimization_iterator != _events.end()) {
762 ++_optimization_iterator;
765 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
766 return *_optimization_iterator;
769 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
770 if ((*_optimization_iterator)->note() == note) {
771 return *_optimization_iterator;
779 MidiRegionView::redisplay_model()
781 // Don't redisplay the model if we're currently recording and displaying that
787 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
791 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
795 MidiModel::ReadLock lock(_model->read_lock());
797 MidiModel::Notes& notes (_model->notes());
798 _optimization_iterator = _events.begin();
800 cerr << "++++++++++ MIDI REdisplay\n";
802 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
804 boost::shared_ptr<NoteType> note (*n);
805 CanvasNoteEvent* cne;
808 if (note_in_region_range (note, visible)) {
810 if ((cne = find_canvas_note (note)) != 0) {
817 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
819 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
831 add_note (note, visible);
836 if ((cne = find_canvas_note (note)) != 0) {
844 /* remove note items that are no longer valid */
846 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
847 if (!(*i)->valid ()) {
849 i = _events.erase (i);
856 display_program_changes();
858 _marked_for_selection.clear ();
859 _marked_for_velocity.clear ();
861 /* we may have caused _events to contain things out of order (e.g. if a note
862 moved earlier or later). we don't generally need them in time order, but
863 make a note that a sort is required for those cases that require it.
870 MidiRegionView::display_program_changes()
872 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
877 Glib::Mutex::Lock lock (control->list()->lock());
879 uint8_t channel = control->parameter().channel();
881 for (AutomationList::const_iterator event = control->list()->begin();
882 event != control->list()->end(); ++event) {
883 double event_time = (*event)->when;
884 double program_number = floor((*event)->value + 0.5);
886 // Get current value of bank select MSB at time of the program change
887 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
888 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
890 if (msb_control != 0) {
891 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
894 // Get current value of bank select LSB at time of the program change
895 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
896 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
898 if (lsb_control != 0) {
899 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
902 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
904 boost::shared_ptr<MIDI::Name::Patch> patch =
905 MIDI::Name::MidiPatchManager::instance().find_patch(
906 _model_name, _custom_device_mode, channel, patch_key);
908 PCEvent program_change(event_time, uint8_t(program_number), channel);
911 add_pgm_change(program_change, patch->name());
914 snprintf(buf, 4, "%d", int(program_number));
915 add_pgm_change(program_change, buf);
921 MidiRegionView::display_sysexes()
923 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
924 Evoral::MusicalTime time = (*i)->time();
929 for (uint32_t b = 0; b < (*i)->size(); ++b) {
930 str << int((*i)->buffer()[b]);
931 if (b != (*i)->size() -1) {
935 string text = str.str();
937 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
939 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
941 double height = midi_stream_view()->contents_height();
943 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
944 new CanvasSysEx(*this, *group, text, height, x, 1.0));
946 // Show unless program change is beyond the region bounds
947 if (time - _region->start() >= _region->length() || time < _region->start()) {
953 _sys_exes.push_back(sysex);
958 MidiRegionView::~MidiRegionView ()
960 in_destructor = true;
962 note_delete_connection.disconnect ();
966 RegionViewGoingAway (this); /* EMIT_SIGNAL */
975 delete _delta_command;
979 MidiRegionView::region_resized (const PropertyChange& what_changed)
981 RegionView::region_resized(what_changed);
983 if (what_changed.contains (ARDOUR::Properties::position)) {
984 set_duration(_region->length(), 0);
985 if (_enable_display) {
992 MidiRegionView::reset_width_dependent_items (double pixel_width)
994 RegionView::reset_width_dependent_items(pixel_width);
995 assert(_pixel_width == pixel_width);
997 if (_enable_display) {
1003 MidiRegionView::set_height (double height)
1005 static const double FUDGE = 2.0;
1006 const double old_height = _height;
1007 RegionView::set_height(height);
1008 _height = height - FUDGE;
1010 apply_note_range(midi_stream_view()->lowest_note(),
1011 midi_stream_view()->highest_note(),
1012 height != old_height + FUDGE);
1015 name_pixbuf->raise_to_top();
1020 /** Apply the current note range from the stream view
1021 * by repositioning/hiding notes as necessary
1024 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1026 if (!_enable_display) {
1030 if (!force && _current_range_min == min && _current_range_max == max) {
1034 _current_range_min = min;
1035 _current_range_max = max;
1037 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1038 CanvasNoteEvent* event = *i;
1039 boost::shared_ptr<NoteType> note (event->note());
1041 if (note->note() < _current_range_min ||
1042 note->note() > _current_range_max) {
1048 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1050 const double y1 = midi_stream_view()->note_to_y(note->note());
1051 const double y2 = y1 + floor(midi_stream_view()->note_height());
1053 cnote->property_y1() = y1;
1054 cnote->property_y2() = y2;
1056 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1058 double x = trackview.editor().frame_to_pixel(
1059 beats_to_frames(note->time()) - _region->start());
1060 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1061 double y = midi_stream_view()->note_to_y(event->note()->note())
1062 + ((diamond_size-2.0) / 4.0);
1064 chit->set_height (diamond_size);
1065 chit->move (x - chit->x1(), y - chit->y1());
1072 MidiRegionView::add_ghost (TimeAxisView& tv)
1076 double unit_position = _region->position () / samples_per_unit;
1077 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1078 MidiGhostRegion* ghost;
1080 if (mtv && mtv->midi_view()) {
1081 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1082 to allow having midi notes on top of note lines and waveforms.
1084 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1086 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1089 ghost->set_height ();
1090 ghost->set_duration (_region->length() / samples_per_unit);
1091 ghosts.push_back (ghost);
1093 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1094 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1095 ghost->add_note(note);
1099 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1105 /** Begin tracking note state for successive calls to add_event
1108 MidiRegionView::begin_write()
1110 assert(!_active_notes);
1111 _active_notes = new CanvasNote*[128];
1112 for (unsigned i=0; i < 128; ++i) {
1113 _active_notes[i] = 0;
1118 /** Destroy note state for add_event
1121 MidiRegionView::end_write()
1123 delete[] _active_notes;
1125 _marked_for_selection.clear();
1126 _marked_for_velocity.clear();
1130 /** Resolve an active MIDI note (while recording).
1133 MidiRegionView::resolve_note(uint8_t note, double end_time)
1135 if (midi_view()->note_mode() != Sustained) {
1139 if (_active_notes && _active_notes[note]) {
1140 const nframes64_t end_time_frames = beats_to_frames(end_time);
1141 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1142 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1143 _active_notes[note] = 0;
1148 /** Extend active notes to rightmost edge of region (if length is changed)
1151 MidiRegionView::extend_active_notes()
1153 if (!_active_notes) {
1157 for (unsigned i=0; i < 128; ++i) {
1158 if (_active_notes[i]) {
1159 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1165 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1167 if (no_sound_notes || !trackview.editor().sound_notes()) {
1171 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1174 route_ui->midi_track()->write_immediate_event(
1175 note->on_event().size(), note->on_event().buffer());
1177 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1178 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1179 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1180 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1181 note_length_ms, G_PRIORITY_DEFAULT);
1185 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1187 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1190 route_ui->midi_track()->write_immediate_event(
1191 note->off_event().size(), note->off_event().buffer());
1197 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1199 const nframes64_t note_start_frames = beats_to_frames(note->time());
1201 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1202 (note_start_frames < _region->start());
1204 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1205 (note->note() <= midi_stream_view()->highest_note());
1211 MidiRegionView::update_note (CanvasNote* ev)
1213 boost::shared_ptr<NoteType> note = ev->note();
1215 const nframes64_t note_start_frames = beats_to_frames(note->time());
1216 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1218 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1219 const double y1 = midi_stream_view()->note_to_y(note->note());
1220 const double note_endpixel =
1221 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1223 ev->property_x1() = x;
1224 ev->property_y1() = y1;
1225 if (note->length() > 0) {
1226 ev->property_x2() = note_endpixel;
1228 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1230 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1232 if (note->length() == 0) {
1233 if (_active_notes) {
1234 assert(note->note() < 128);
1235 // If this note is already active there's a stuck note,
1236 // finish the old note rectangle
1237 if (_active_notes[note->note()]) {
1238 CanvasNote* const old_rect = _active_notes[note->note()];
1239 boost::shared_ptr<NoteType> old_note = old_rect->note();
1240 old_rect->property_x2() = x;
1241 old_rect->property_outline_what() = (guint32) 0xF;
1243 _active_notes[note->note()] = ev;
1245 /* outline all but right edge */
1246 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1248 /* outline all edges */
1249 ev->property_outline_what() = (guint32) 0xF;
1254 MidiRegionView::update_hit (CanvasHit* ev)
1256 boost::shared_ptr<NoteType> note = ev->note();
1258 const nframes64_t note_start_frames = beats_to_frames(note->time());
1259 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1260 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1261 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1266 /** Add a MIDI note to the view (with length).
1268 * If in sustained mode, notes with length 0 will be considered active
1269 * notes, and resolve_note should be called when the corresponding note off
1270 * event arrives, to properly display the note.
1273 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1275 CanvasNoteEvent* event = 0;
1277 assert(note->time() >= 0);
1278 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1280 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1282 if (midi_view()->note_mode() == Sustained) {
1284 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1286 update_note (ev_rect);
1290 MidiGhostRegion* gr;
1292 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1293 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1294 gr->add_note(ev_rect);
1298 } else if (midi_view()->note_mode() == Percussive) {
1300 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1302 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1304 update_hit (ev_diamond);
1313 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1314 note_selected(event, true);
1317 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1318 event->show_velocity();
1320 event->on_channel_selection_change(_last_channel_selection);
1321 _events.push_back(event);
1332 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1333 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1335 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1337 start_delta_command (_("step add"));
1338 delta_add_note (new_note, true, false);
1341 /* potentially extend region to hold new note */
1343 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1344 nframes64_t region_end = _region->position() + _region->length() - 1;
1346 if (end_frame > region_end) {
1347 _region->set_length (end_frame, this);
1354 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1356 assert(program.time >= 0);
1358 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1359 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1361 double height = midi_stream_view()->contents_height();
1363 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1364 new CanvasProgramChange(*this, *group,
1369 _custom_device_mode,
1370 program.time, program.channel, program.value));
1372 // Show unless program change is beyond the region bounds
1373 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1379 _pgm_changes.push_back(pgm_change);
1383 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1385 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1386 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1387 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1389 if (msb_control != 0) {
1390 msb = int(msb_control->get_float(true, time));
1391 cerr << "got msb " << msb;
1394 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1395 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1397 if (lsb_control != 0) {
1398 lsb = lsb_control->get_float(true, time);
1399 cerr << " got lsb " << lsb;
1402 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1403 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1404 float program_number = -1.0;
1405 if (program_control != 0) {
1406 program_number = program_control->get_float(true, time);
1407 cerr << " got program " << program_number << endl;
1410 key.msb = (int) floor(msb + 0.5);
1411 key.lsb = (int) floor(lsb + 0.5);
1412 key.program_number = (int) floor(program_number + 0.5);
1413 assert(key.is_sane());
1418 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1420 // TODO: Get the real event here and alter them at the original times
1421 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1422 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1423 if (msb_control != 0) {
1424 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1427 // TODO: Get the real event here and alter them at the original times
1428 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1429 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1430 if (lsb_control != 0) {
1431 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1434 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1435 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1437 assert(program_control != 0);
1438 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1444 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1446 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1447 alter_program_change(program_change_event, new_patch);
1451 MidiRegionView::previous_program(CanvasProgramChange& program)
1453 MIDI::Name::PatchPrimaryKey key;
1454 get_patch_key_at(program.event_time(), program.channel(), key);
1456 boost::shared_ptr<MIDI::Name::Patch> patch =
1457 MIDI::Name::MidiPatchManager::instance().previous_patch(
1459 _custom_device_mode,
1463 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1465 alter_program_change(program_change_event, patch->patch_primary_key());
1470 MidiRegionView::next_program(CanvasProgramChange& program)
1472 MIDI::Name::PatchPrimaryKey key;
1473 get_patch_key_at(program.event_time(), program.channel(), key);
1475 boost::shared_ptr<MIDI::Name::Patch> patch =
1476 MIDI::Name::MidiPatchManager::instance().next_patch(
1478 _custom_device_mode,
1482 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1484 alter_program_change(program_change_event, patch->patch_primary_key());
1489 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1491 if (_selection.empty()) {
1495 if (_selection.erase (cne) > 0) {
1496 cerr << "Erased a CNE from selection\n";
1501 MidiRegionView::delete_selection()
1503 if (_selection.empty()) {
1507 start_delta_command (_("delete selection"));
1509 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1510 if ((*i)->selected()) {
1511 _delta_command->remove((*i)->note());
1521 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1523 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1524 if ((*i)->selected() && (*i) != ev) {
1525 (*i)->selected(false);
1526 (*i)->hide_velocity();
1534 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1536 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1539 Selection::iterator tmp = i;
1542 (*i)->selected (false);
1543 _selection.erase (i);
1552 /* don't bother with removing this regionview from the editor selection,
1553 since we're about to add another note, and thus put/keep this
1554 regionview in the editor selection.
1557 if (!ev->selected()) {
1558 add_to_selection (ev);
1563 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1565 uint8_t low_note = 127;
1566 uint8_t high_note = 0;
1567 MidiModel::Notes& notes (_model->notes());
1568 _optimization_iterator = _events.begin();
1570 if (extend && _selection.empty()) {
1576 /* scan existing selection to get note range */
1578 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1579 if ((*i)->note()->note() < low_note) {
1580 low_note = (*i)->note()->note();
1582 if ((*i)->note()->note() > high_note) {
1583 high_note = (*i)->note()->note();
1587 low_note = min (low_note, notenum);
1588 high_note = max (high_note, notenum);
1591 no_sound_notes = true;
1593 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1595 boost::shared_ptr<NoteType> note (*n);
1596 CanvasNoteEvent* cne;
1597 bool select = false;
1599 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1601 if ((note->note() >= low_note && note->note() <= high_note)) {
1604 } else if (note->note() == notenum) {
1610 if ((cne = find_canvas_note (note)) != 0) {
1611 // extend is false because we've taken care of it,
1612 // since it extends by time range, not pitch.
1613 note_selected (cne, add, false);
1617 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1621 no_sound_notes = false;
1625 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1627 MidiModel::Notes& notes (_model->notes());
1628 _optimization_iterator = _events.begin();
1630 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1632 boost::shared_ptr<NoteType> note (*n);
1633 CanvasNoteEvent* cne;
1635 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1636 if ((cne = find_canvas_note (note)) != 0) {
1637 if (cne->selected()) {
1638 note_deselected (cne);
1640 note_selected (cne, true, false);
1648 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1651 clear_selection_except(ev);
1656 if (!ev->selected()) {
1657 add_to_selection (ev);
1661 /* find end of latest note selected, select all between that and the start of "ev" */
1663 Evoral::MusicalTime earliest = DBL_MAX;
1664 Evoral::MusicalTime latest = 0;
1666 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1667 if ((*i)->note()->end_time() > latest) {
1668 latest = (*i)->note()->end_time();
1670 if ((*i)->note()->time() < earliest) {
1671 earliest = (*i)->note()->time();
1675 if (ev->note()->end_time() > latest) {
1676 latest = ev->note()->end_time();
1679 if (ev->note()->time() < earliest) {
1680 earliest = ev->note()->time();
1683 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1685 /* find notes entirely within OR spanning the earliest..latest range */
1687 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1688 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1689 add_to_selection (*i);
1693 /* if events were guaranteed to be time sorted, we could do this.
1694 but as of sept 10th 2009, they no longer are.
1697 if ((*i)->note()->time() > latest) {
1706 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1708 remove_from_selection (ev);
1712 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1722 // TODO: Make this faster by storing the last updated selection rect, and only
1723 // adjusting things that are in the area that appears/disappeared.
1724 // We probably need a tree to be able to find events in O(log(n)) time.
1726 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1728 /* check if any corner of the note is inside the rect
1731 1) this is computing "touched by", not "contained by" the rect.
1732 2) this does not require that events be sorted in time.
1735 const double ix1 = (*i)->x1();
1736 const double ix2 = (*i)->x2();
1737 const double iy1 = (*i)->y1();
1738 const double iy2 = (*i)->y2();
1740 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1741 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1742 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1743 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1746 if (!(*i)->selected()) {
1747 add_to_selection (*i);
1749 } else if ((*i)->selected()) {
1750 // Not inside rectangle
1751 remove_from_selection (*i);
1757 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1759 Selection::iterator i = _selection.find (ev);
1761 if (i != _selection.end()) {
1762 _selection.erase (i);
1765 ev->selected (false);
1766 ev->hide_velocity ();
1768 if (_selection.empty()) {
1769 PublicEditor& editor (trackview.editor());
1770 editor.get_selection().remove (this);
1775 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1777 bool add_mrv_selection = false;
1779 if (_selection.empty()) {
1780 add_mrv_selection = true;
1783 if (_selection.insert (ev).second) {
1784 ev->selected (true);
1785 play_midi_note ((ev)->note());
1788 if (add_mrv_selection) {
1789 PublicEditor& editor (trackview.editor());
1790 editor.get_selection().add (this);
1795 MidiRegionView::move_selection(double dx, double dy)
1797 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1798 (*i)->move_event(dx, dy);
1803 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1805 assert (!_selection.empty());
1807 uint8_t lowest_note_in_selection = 127;
1808 uint8_t highest_note_in_selection = 0;
1809 uint8_t highest_note_difference = 0;
1811 // find highest and lowest notes first
1813 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1814 uint8_t pitch = (*i)->note()->note();
1815 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1816 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1820 cerr << "dnote: " << (int) dnote << endl;
1821 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1822 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1823 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1824 << int(highest_note_in_selection) << endl;
1825 cerr << "selection size: " << _selection.size() << endl;
1826 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1829 // Make sure the note pitch does not exceed the MIDI standard range
1830 if (highest_note_in_selection + dnote > 127) {
1831 highest_note_difference = highest_note_in_selection - 127;
1834 start_diff_command(_("move notes"));
1836 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1838 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1841 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1843 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1846 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1852 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1854 uint8_t original_pitch = (*i)->note()->note();
1855 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1857 // keep notes in standard midi range
1858 clamp_to_0_127(new_pitch);
1860 // keep original pitch if note is dragged outside valid midi range
1861 if ((original_pitch != 0 && new_pitch == 0)
1862 || (original_pitch != 127 && new_pitch == 127)) {
1863 new_pitch = original_pitch;
1866 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1867 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1869 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1874 // care about notes being moved beyond the upper/lower bounds on the canvas
1875 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1876 highest_note_in_selection > midi_stream_view()->highest_note()) {
1877 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1882 MidiRegionView::snap_pixel_to_frame(double x)
1884 PublicEditor& editor = trackview.editor();
1885 // x is region relative, convert it to global absolute frames
1886 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1887 editor.snap_to(frame);
1888 return frame - _region->position(); // convert back to region relative
1892 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1894 PublicEditor& editor = trackview.editor();
1895 // x is region relative, convert it to global absolute frames
1896 nframes64_t frame = x + _region->position();
1897 editor.snap_to(frame);
1898 return frame - _region->position(); // convert back to region relative
1902 MidiRegionView::snap_to_pixel(double x)
1904 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1908 MidiRegionView::get_position_pixels()
1910 nframes64_t region_frame = get_position();
1911 return trackview.editor().frame_to_pixel(region_frame);
1915 MidiRegionView::get_end_position_pixels()
1917 nframes64_t frame = get_position() + get_duration ();
1918 return trackview.editor().frame_to_pixel(frame);
1922 MidiRegionView::beats_to_frames(double beats) const
1924 return _time_converter.to(beats);
1928 MidiRegionView::frames_to_beats(nframes64_t frames) const
1930 return _time_converter.from(frames);
1934 MidiRegionView::begin_resizing (bool /*at_front*/)
1936 _resize_data.clear();
1938 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1939 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1941 // only insert CanvasNotes into the map
1943 NoteResizeData *resize_data = new NoteResizeData();
1944 resize_data->canvas_note = note;
1946 // create a new SimpleRect from the note which will be the resize preview
1947 SimpleRect *resize_rect = new SimpleRect(
1948 *group, note->x1(), note->y1(), note->x2(), note->y2());
1950 // calculate the colors: get the color settings
1951 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1952 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1955 // make the resize preview notes more transparent and bright
1956 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1958 // calculate color based on note velocity
1959 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1960 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1964 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1965 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1967 resize_data->resize_rect = resize_rect;
1968 _resize_data.push_back(resize_data);
1974 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1976 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1977 SimpleRect* resize_rect = (*i)->resize_rect;
1978 CanvasNote* canvas_note = (*i)->canvas_note;
1983 current_x = canvas_note->x1() + delta_x;
1985 // x is in track relative, transform it to region relative
1986 current_x = delta_x - get_position_pixels();
1990 current_x = canvas_note->x2() + delta_x;
1992 // x is in track relative, transform it to region relative
1993 current_x = delta_x - get_end_position_pixels ();
1998 resize_rect->property_x1() = snap_to_pixel(current_x);
1999 resize_rect->property_x2() = canvas_note->x2();
2001 resize_rect->property_x2() = snap_to_pixel(current_x);
2002 resize_rect->property_x1() = canvas_note->x1();
2008 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
2010 start_diff_command(_("resize notes"));
2012 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2013 CanvasNote* canvas_note = (*i)->canvas_note;
2014 SimpleRect* resize_rect = (*i)->resize_rect;
2015 const double region_start = get_position_pixels();
2020 current_x = canvas_note->x1() + delta_x;
2022 // x is in track relative, transform it to region relative
2023 current_x = region_start + delta_x;
2027 current_x = canvas_note->x2() + delta_x;
2029 // x is in track relative, transform it to region relative
2030 current_x = region_start + delta_x;
2034 current_x = snap_pixel_to_frame (current_x);
2035 current_x = frames_to_beats (current_x);
2037 if (at_front && current_x < canvas_note->note()->end_time()) {
2038 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2040 double len = canvas_note->note()->time() - current_x;
2041 len += canvas_note->note()->length();
2044 /* XXX convert to beats */
2045 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2050 double len = current_x - canvas_note->note()->time();
2053 /* XXX convert to beats */
2054 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2062 _resize_data.clear();
2067 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2069 uint8_t new_velocity;
2072 new_velocity = event->note()->velocity() + velocity;
2073 clamp_to_0_127(new_velocity);
2075 new_velocity = velocity;
2078 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2082 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2087 new_note = event->note()->note() + note;
2092 clamp_to_0_127 (new_note);
2093 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2097 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2099 bool change_start = false;
2100 bool change_length = false;
2101 Evoral::MusicalTime new_start;
2102 Evoral::MusicalTime new_length;
2104 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2106 front_delta: if positive - move the start of the note later in time (shortening it)
2107 if negative - move the start of the note earlier in time (lengthening it)
2109 end_delta: if positive - move the end of the note later in time (lengthening it)
2110 if negative - move the end of the note earlier in time (shortening it)
2114 if (front_delta < 0) {
2116 if (event->note()->time() < -front_delta) {
2119 new_start = event->note()->time() + front_delta; // moves earlier
2122 /* start moved toward zero, so move the end point out to where it used to be.
2123 Note that front_delta is negative, so this increases the length.
2126 new_length = event->note()->length() - front_delta;
2127 change_start = true;
2128 change_length = true;
2132 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2134 if (new_pos < event->note()->end_time()) {
2135 new_start = event->note()->time() + front_delta;
2136 /* start moved toward the end, so move the end point back to where it used to be */
2137 new_length = event->note()->length() - front_delta;
2138 change_start = true;
2139 change_length = true;
2146 bool can_change = true;
2147 if (end_delta < 0) {
2148 if (event->note()->length() < -end_delta) {
2154 new_length = event->note()->length() + end_delta;
2155 change_length = true;
2160 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2163 if (change_length) {
2164 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2169 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2171 Evoral::MusicalTime new_time;
2175 if (event->note()->time() < -delta) {
2178 new_time = event->note()->time() + delta;
2181 new_time = event->note()->time() + delta;
2187 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2191 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2195 if (_selection.empty()) {
2210 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2211 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2217 start_diff_command(_("change velocities"));
2219 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2220 Selection::iterator next = i;
2222 change_note_velocity (*i, delta, true);
2231 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2233 if (_selection.empty()) {
2250 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2252 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2256 if ((int8_t) (*i)->note()->note() + delta > 127) {
2263 start_diff_command (_("transpose"));
2265 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2266 Selection::iterator next = i;
2268 change_note_note (*i, delta, true);
2276 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2278 Evoral::MusicalTime delta;
2283 /* grab the current grid distance */
2285 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2287 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2288 cerr << "Grid type not available as beats - TO BE FIXED\n";
2297 start_diff_command (_("change note lengths"));
2299 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2300 Selection::iterator next = i;
2303 /* note the negation of the delta for start */
2305 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2314 MidiRegionView::nudge_notes (bool forward)
2316 if (_selection.empty()) {
2320 /* pick a note as the point along the timeline to get the nudge distance.
2321 its not necessarily the earliest note, so we may want to pull the notes out
2322 into a vector and sort before using the first one.
2325 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2327 nframes64_t distance;
2329 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2331 /* grid is off - use nudge distance */
2333 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2339 nframes64_t next_pos = ref_point;
2342 /* XXX need check on max_frames, but that needs max_frames64 or something */
2345 if (next_pos == 0) {
2351 cerr << "ref point was " << ref_point << " next was " << next_pos;
2352 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2353 distance = ref_point - next_pos;
2354 cerr << " final is " << next_pos << " distance = " << distance << endl;
2357 if (distance == 0) {
2361 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2367 start_diff_command (_("nudge"));
2369 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2370 Selection::iterator next = i;
2372 change_note_time (*i, delta, true);
2380 MidiRegionView::change_channel(uint8_t channel)
2382 start_diff_command(_("change channel"));
2383 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2384 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2391 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2393 if (_mouse_state == SelectTouchDragging) {
2394 note_selected(ev, true);
2398 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2399 // This causes an infinite loop on note add sometimes
2400 //PublicEditor& editor (trackview.editor());
2401 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2402 //editor.show_verbose_canvas_cursor_with (buf);
2406 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2408 PublicEditor& editor (trackview.editor());
2409 editor.hide_verbose_canvas_cursor ();
2414 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2416 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2418 display_model(msrc->model());
2422 MidiRegionView::set_frame_color()
2425 if (_selected && should_show_selection) {
2426 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2428 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2434 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2438 case FilterChannels:
2439 _force_channel = -1;
2442 _force_channel = mask;
2443 mask = 0xFFFF; // Show all notes as active (below)
2446 // Update notes for selection
2447 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2448 (*i)->on_channel_selection_change(mask);
2451 _last_channel_selection = mask;
2455 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2457 _model_name = model;
2458 _custom_device_mode = custom_device_mode;
2463 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2465 if (_selection.empty()) {
2469 PublicEditor& editor (trackview.editor());
2474 editor.get_cut_buffer().add (selection_as_cut_buffer());
2482 start_delta_command();
2484 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2490 delta_remove_note (*i);
2500 MidiRegionView::selection_as_cut_buffer () const
2504 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2505 NoteType* n = (*i)->note().get();
2506 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2509 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2516 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2522 start_delta_command (_("paste"));
2524 Evoral::MusicalTime beat_delta;
2525 Evoral::MusicalTime paste_pos_beats;
2526 Evoral::MusicalTime duration;
2527 Evoral::MusicalTime end_point;
2529 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2530 paste_pos_beats = frames_to_beats (pos - _region->position());
2531 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2532 paste_pos_beats = 0;
2534 _selection.clear ();
2536 for (int n = 0; n < (int) times; ++n) {
2538 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2540 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2542 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2543 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2545 /* make all newly added notes selected */
2547 delta_add_note (copied_note, true);
2548 end_point = copied_note->end_time();
2551 paste_pos_beats += duration;
2554 /* if we pasted past the current end of the region, extend the region */
2556 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2557 nframes64_t region_end = _region->position() + _region->length() - 1;
2559 if (end_frame > region_end) {
2561 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2563 trackview.session()->begin_reversible_command (_("paste"));
2565 _region->clear_history ();
2566 _region->set_length (end_frame, this);
2567 trackview.session()->add_command (new StatefulDiffCommand (_region));
2570 cerr << "region end finally at " << _region->position() + _region->length() - 1;
2574 struct EventNoteTimeEarlyFirstComparator {
2575 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2576 return a->note()->time() < b->note()->time();
2581 MidiRegionView::time_sort_events ()
2583 if (!_sort_needed) {
2587 EventNoteTimeEarlyFirstComparator cmp;
2590 _sort_needed = false;
2594 MidiRegionView::goto_next_note ()
2596 // nframes64_t pos = -1;
2597 bool use_next = false;
2599 if (_events.back()->selected()) {
2603 time_sort_events ();
2605 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2606 if ((*i)->selected()) {
2609 } else if (use_next) {
2611 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2616 /* use the first one */
2618 unique_select (_events.front());
2623 MidiRegionView::goto_previous_note ()
2625 // nframes64_t pos = -1;
2626 bool use_next = false;
2628 if (_events.front()->selected()) {
2632 time_sort_events ();
2634 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2635 if ((*i)->selected()) {
2638 } else if (use_next) {
2640 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2645 /* use the last one */
2647 unique_select (*(_events.rbegin()));
2651 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2653 bool had_selected = false;
2655 time_sort_events ();
2657 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2658 if ((*i)->selected()) {
2659 selected.insert ((*i)->note());
2660 had_selected = true;
2664 if (allow_all_if_none_selected && !had_selected) {
2665 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2666 selected.insert ((*i)->note());