2 Copyright (C) 2001-2007 Paul Davis
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
33 #include "ardour/playlist.h"
34 #include "ardour/tempo.h"
35 #include "ardour/midi_region.h"
36 #include "ardour/midi_source.h"
37 #include "ardour/midi_diskstream.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
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 , _default_note_length(1.0)
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())
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 , _default_note_length(1.0)
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())
117 _note_group->raise_to_top();
121 MidiRegionView::MidiRegionView (const MidiRegionView& other)
122 : sigc::trackable(other)
125 , _last_channel_selection(0xFFFF)
126 , _default_note_length(1.0)
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())
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 , _default_note_length(1.0)
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())
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)
178 midi_region()->midi_source(0)->load_model();
181 _model = midi_region()->midi_source(0)->model();
182 _enable_display = false;
184 RegionView::init (basic_color, false);
186 compute_colors (basic_color);
188 set_height (trackview.current_height());
191 region_sync_changed ();
192 region_resized (BoundsChanged);
195 reset_width_dependent_items (_pixel_width);
199 _enable_display = true;
202 display_model (_model);
206 group->raise_to_top();
207 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
209 midi_view()->signal_channel_mode_changed().connect(
210 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
212 midi_view()->signal_midi_patch_settings_changed().connect(
213 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
217 MidiRegionView::canvas_event(GdkEvent* ev)
219 PublicEditor& editor (trackview.editor());
221 if (!editor.internal_editing()) {
225 static double drag_start_x, drag_start_y;
226 static double last_x, last_y;
227 double event_x, event_y;
228 nframes64_t event_frame = 0;
231 static ArdourCanvas::SimpleRect* drag_rect = 0;
233 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
234 to its items, which means that ev->type == GDK_SCROLL will never be seen
239 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
241 if (ev->scroll.direction == GDK_SCROLL_UP) {
242 change_velocities (true, fine, false);
244 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
245 change_velocities (false, fine, false);
254 /* since GTK bindings are generally activated on press, and since
255 detectable auto-repeat is the name of the game and only sends
256 repeated presses, carry out key actions at key press, not release.
259 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
260 _mouse_state = SelectTouchDragging;
263 } else if (ev->key.keyval == GDK_Escape) {
267 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
269 bool start = (ev->key.keyval == GDK_comma);
270 bool end = (ev->key.keyval == GDK_period);
271 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
272 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
274 change_note_lengths (fine, shorter, start, end);
278 } else if (ev->key.keyval == GDK_Delete) {
283 } else if (ev->key.keyval == GDK_Tab) {
285 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
286 goto_previous_note ();
292 } else if (ev->key.keyval == GDK_Up) {
294 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
295 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
297 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
298 change_velocities (true, fine, allow_smush);
300 transpose (true, fine, allow_smush);
304 } else if (ev->key.keyval == GDK_Down) {
306 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
307 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
309 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
310 change_velocities (false, fine, allow_smush);
312 transpose (false, fine, allow_smush);
316 } else if (ev->key.keyval == GDK_Left) {
321 } else if (ev->key.keyval == GDK_Right) {
326 } else if (ev->key.keyval == GDK_Control_L) {
329 } else if (ev->key.keyval == GDK_r) {
330 /* if we're not step editing, this really doesn't matter */
331 midi_view()->step_edit_rest ();
337 case GDK_KEY_RELEASE:
338 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
344 case GDK_BUTTON_PRESS:
345 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
346 _pressed_button = ev->button.button;
347 _mouse_state = Pressed;
350 _pressed_button = ev->button.button;
353 case GDK_2BUTTON_PRESS:
356 case GDK_ENTER_NOTIFY:
357 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
358 Keyboard::magic_widget_grab_focus();
362 case GDK_MOTION_NOTIFY:
363 event_x = ev->motion.x;
364 event_y = ev->motion.y;
365 group->w2i(event_x, event_y);
367 // convert event_x to global frame
368 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
369 trackview.editor().snap_to(event_frame);
370 // convert event_frame back to local coordinates relative to position
371 event_frame -= _region->position();
373 switch (_mouse_state) {
374 case Pressed: // Drag start
377 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
378 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
379 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
382 drag_start_x = event_x;
383 drag_start_y = event_y;
385 drag_rect = new ArdourCanvas::SimpleRect(*group);
386 drag_rect->property_x1() = event_x;
387 drag_rect->property_y1() = event_y;
388 drag_rect->property_x2() = event_x;
389 drag_rect->property_y2() = event_y;
390 drag_rect->property_outline_what() = 0xFF;
391 drag_rect->property_outline_color_rgba()
392 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
393 drag_rect->property_fill_color_rgba()
394 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
396 _mouse_state = SelectRectDragging;
399 // Add note drag start
400 } else if (editor.current_mouse_mode() == MouseRange) {
401 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
402 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
405 drag_start_x = event_x;
406 drag_start_y = event_y;
408 drag_rect = new ArdourCanvas::SimpleRect(*group);
409 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
411 drag_rect->property_y1() = midi_stream_view()->note_to_y(
412 midi_stream_view()->y_to_note(event_y));
413 drag_rect->property_x2() = event_x;
414 drag_rect->property_y2() = drag_rect->property_y1()
415 + floor(midi_stream_view()->note_height());
416 drag_rect->property_outline_what() = 0xFF;
417 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
418 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
420 _mouse_state = AddDragging;
426 case SelectRectDragging: // Select drag motion
427 case AddDragging: // Add note drag motion
428 if (ev->motion.is_hint) {
431 GdkModifierType state;
432 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
437 if (_mouse_state == AddDragging)
438 event_x = trackview.editor().frame_to_pixel(event_frame);
441 if (event_x > drag_start_x)
442 drag_rect->property_x2() = event_x;
444 drag_rect->property_x1() = event_x;
447 if (drag_rect && _mouse_state == SelectRectDragging) {
448 if (event_y > drag_start_y)
449 drag_rect->property_y2() = event_y;
451 drag_rect->property_y1() = event_y;
453 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
459 case SelectTouchDragging:
467 case GDK_BUTTON_RELEASE:
468 event_x = ev->motion.x;
469 event_y = ev->motion.y;
470 group->w2i(event_x, event_y);
471 group->ungrab(ev->button.time);
472 event_frame = trackview.editor().pixel_to_frame(event_x);
474 if (ev->button.button == 3) {
476 } else if (_pressed_button != 1) {
480 switch (_mouse_state) {
481 case Pressed: // Clicked
482 switch (editor.current_mouse_mode()) {
488 create_note_at(event_x, event_y, _default_note_length);
495 case SelectRectDragging: // Select drag done
500 case AddDragging: // Add drag done
502 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
503 const double x = drag_rect->property_x1();
504 const double length = trackview.editor().pixel_to_frame(
505 drag_rect->property_x2() - drag_rect->property_x1());
507 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
522 MidiRegionView::show_list_editor ()
525 _list_editor = new MidiListEditor (trackview.session(), midi_region());
527 _list_editor->present ();
530 /** Add a note to the model, and the view, at a canvas (click) coordinate.
531 * \param x horizontal position in pixels
532 * \param y vertical position in pixels
533 * \param length duration of the note in beats */
535 MidiRegionView::create_note_at(double x, double y, double length)
537 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
538 MidiStreamView* const view = mtv->midi_view();
540 double note = midi_stream_view()->y_to_note(y);
543 assert(note <= 127.0);
545 // Start of note in frames relative to region start
546 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
547 assert(start_frames >= 0);
550 length = frames_to_beats(
551 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
553 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
554 frames_to_beats(start_frames + _region->start()), length,
555 (uint8_t)note, 0x40));
557 view->update_note_range(new_note->note());
559 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
561 _model->apply_command(*trackview.session(), cmd);
563 play_midi_note (new_note);
567 MidiRegionView::clear_events()
572 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
573 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
578 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
583 _pgm_changes.clear();
585 _optimization_iterator = _events.end();
590 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
593 content_connection.disconnect ();
594 _model->ContentsChanged.connect (content_connection, boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
598 if (_enable_display) {
605 MidiRegionView::start_delta_command(string name)
607 if (!_delta_command) {
608 _delta_command = _model->new_delta_command(name);
613 MidiRegionView::start_diff_command(string name)
615 if (!_diff_command) {
616 _diff_command = _model->new_diff_command(name);
621 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
623 if (_delta_command) {
624 _delta_command->add(note);
627 _marked_for_selection.insert(note);
630 _marked_for_velocity.insert(note);
635 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
637 if (_delta_command && ev->note()) {
638 _delta_command->remove(ev->note());
643 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
644 MidiModel::DiffCommand::Property property,
648 _diff_command->change (ev->note(), property, val);
653 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
654 MidiModel::DiffCommand::Property property,
655 Evoral::MusicalTime val)
658 _diff_command->change (ev->note(), property, val);
663 MidiRegionView::apply_delta()
665 if (!_delta_command) {
669 // Mark all selected notes for selection when model reloads
670 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
671 _marked_for_selection.insert((*i)->note());
674 _model->apply_command(*trackview.session(), _delta_command);
676 midi_view()->midi_track()->diskstream()->playlist_modified();
678 _marked_for_selection.clear();
679 _marked_for_velocity.clear();
683 MidiRegionView::apply_diff ()
685 if (!_diff_command) {
689 _model->apply_command(*trackview.session(), _diff_command);
691 midi_view()->midi_track()->diskstream()->playlist_modified();
693 _marked_for_velocity.clear();
697 MidiRegionView::apply_delta_as_subcommand()
699 if (!_delta_command) {
703 // Mark all selected notes for selection when model reloads
704 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
705 _marked_for_selection.insert((*i)->note());
708 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
710 midi_view()->midi_track()->diskstream()->playlist_modified();
712 _marked_for_selection.clear();
713 _marked_for_velocity.clear();
717 MidiRegionView::apply_diff_as_subcommand()
719 if (!_diff_command) {
723 // Mark all selected notes for selection when model reloads
724 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
725 _marked_for_selection.insert((*i)->note());
728 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
730 midi_view()->midi_track()->diskstream()->playlist_modified();
732 _marked_for_selection.clear();
733 _marked_for_velocity.clear();
737 MidiRegionView::abort_command()
739 delete _delta_command;
741 delete _diff_command;
747 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
749 if (_optimization_iterator != _events.end()) {
750 ++_optimization_iterator;
753 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
754 return *_optimization_iterator;
757 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
758 if ((*_optimization_iterator)->note() == note) {
759 return *_optimization_iterator;
767 MidiRegionView::redisplay_model()
769 // Don't redisplay the model if we're currently recording and displaying that
775 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
779 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
783 MidiModel::ReadLock lock(_model->read_lock());
785 MidiModel::Notes& notes (_model->notes());
786 _optimization_iterator = _events.begin();
788 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
790 boost::shared_ptr<NoteType> note (*n);
791 CanvasNoteEvent* cne;
794 if (note_in_region_range (note, visible)) {
796 if ((cne = find_canvas_note (note)) != 0) {
803 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
805 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
817 add_note (note, visible);
822 if ((cne = find_canvas_note (note)) != 0) {
829 /* remove note items that are no longer valid */
831 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
832 if (!(*i)->valid ()) {
834 i = _events.erase (i);
841 display_program_changes();
843 _marked_for_selection.clear ();
844 _marked_for_velocity.clear ();
846 /* we may have caused _events to contain things out of order (e.g. if a note
847 moved earlier or later). we don't generally need them in time order, but
848 make a note that a sort is required for those cases that require it.
855 MidiRegionView::display_program_changes()
857 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
862 Glib::Mutex::Lock lock (control->list()->lock());
864 uint8_t channel = control->parameter().channel();
866 for (AutomationList::const_iterator event = control->list()->begin();
867 event != control->list()->end(); ++event) {
868 double event_time = (*event)->when;
869 double program_number = floor((*event)->value + 0.5);
871 // Get current value of bank select MSB at time of the program change
872 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
873 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
875 if (msb_control != 0) {
876 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
879 // Get current value of bank select LSB at time of the program change
880 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
881 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
883 if (lsb_control != 0) {
884 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
887 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
889 boost::shared_ptr<MIDI::Name::Patch> patch =
890 MIDI::Name::MidiPatchManager::instance().find_patch(
891 _model_name, _custom_device_mode, channel, patch_key);
893 PCEvent program_change(event_time, uint8_t(program_number), channel);
896 add_pgm_change(program_change, patch->name());
899 snprintf(buf, 4, "%d", int(program_number));
900 add_pgm_change(program_change, buf);
906 MidiRegionView::display_sysexes()
908 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
909 Evoral::MusicalTime time = (*i)->time();
914 for (uint32_t b = 0; b < (*i)->size(); ++b) {
915 str << int((*i)->buffer()[b]);
916 if (b != (*i)->size() -1) {
920 string text = str.str();
922 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
924 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
926 double height = midi_stream_view()->contents_height();
928 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
929 new CanvasSysEx(*this, *group, text, height, x, 1.0));
931 // Show unless program change is beyond the region bounds
932 if (time - _region->start() >= _region->length() || time < _region->start()) {
938 _sys_exes.push_back(sysex);
943 MidiRegionView::~MidiRegionView ()
945 in_destructor = true;
949 RegionViewGoingAway (this); /* EMIT_SIGNAL */
958 delete _delta_command;
962 MidiRegionView::region_resized (Change what_changed)
964 RegionView::region_resized(what_changed);
966 if (what_changed & ARDOUR::PositionChanged) {
967 set_duration(_region->length(), 0);
968 if (_enable_display) {
975 MidiRegionView::reset_width_dependent_items (double pixel_width)
977 RegionView::reset_width_dependent_items(pixel_width);
978 assert(_pixel_width == pixel_width);
980 if (_enable_display) {
986 MidiRegionView::set_height (double height)
988 static const double FUDGE = 2.0;
989 const double old_height = _height;
990 RegionView::set_height(height);
991 _height = height - FUDGE;
993 apply_note_range(midi_stream_view()->lowest_note(),
994 midi_stream_view()->highest_note(),
995 height != old_height + FUDGE);
998 name_pixbuf->raise_to_top();
1003 /** Apply the current note range from the stream view
1004 * by repositioning/hiding notes as necessary
1007 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1009 if (!_enable_display) {
1013 if (!force && _current_range_min == min && _current_range_max == max) {
1017 _current_range_min = min;
1018 _current_range_max = max;
1020 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1021 CanvasNoteEvent* event = *i;
1022 boost::shared_ptr<NoteType> note (event->note());
1024 if (note->note() < _current_range_min ||
1025 note->note() > _current_range_max) {
1031 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1033 const double y1 = midi_stream_view()->note_to_y(note->note());
1034 const double y2 = y1 + floor(midi_stream_view()->note_height());
1036 cnote->property_y1() = y1;
1037 cnote->property_y2() = y2;
1039 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1041 double x = trackview.editor().frame_to_pixel(
1042 beats_to_frames(note->time()) - _region->start());
1043 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1044 double y = midi_stream_view()->note_to_y(event->note()->note())
1045 + ((diamond_size-2.0) / 4.0);
1047 chit->set_height (diamond_size);
1048 chit->move (x - chit->x1(), y - chit->y1());
1055 MidiRegionView::add_ghost (TimeAxisView& tv)
1059 double unit_position = _region->position () / samples_per_unit;
1060 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1061 MidiGhostRegion* ghost;
1063 if (mtv && mtv->midi_view()) {
1064 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1065 to allow having midi notes on top of note lines and waveforms.
1067 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1069 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1072 ghost->set_height ();
1073 ghost->set_duration (_region->length() / samples_per_unit);
1074 ghosts.push_back (ghost);
1076 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1077 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1078 ghost->add_note(note);
1082 GhostRegion::CatchDeletion.connect (*this, ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1088 /** Begin tracking note state for successive calls to add_event
1091 MidiRegionView::begin_write()
1093 assert(!_active_notes);
1094 _active_notes = new CanvasNote*[128];
1095 for (unsigned i=0; i < 128; ++i) {
1096 _active_notes[i] = 0;
1101 /** Destroy note state for add_event
1104 MidiRegionView::end_write()
1106 delete[] _active_notes;
1108 _marked_for_selection.clear();
1109 _marked_for_velocity.clear();
1113 /** Resolve an active MIDI note (while recording).
1116 MidiRegionView::resolve_note(uint8_t note, double end_time)
1118 if (midi_view()->note_mode() != Sustained) {
1122 if (_active_notes && _active_notes[note]) {
1123 const nframes64_t end_time_frames = beats_to_frames(end_time);
1124 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1125 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1126 _active_notes[note] = 0;
1131 /** Extend active notes to rightmost edge of region (if length is changed)
1134 MidiRegionView::extend_active_notes()
1136 if (!_active_notes) {
1140 for (unsigned i=0; i < 128; ++i) {
1141 if (_active_notes[i]) {
1142 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1148 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1150 if (!trackview.editor().sound_notes()) {
1154 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1157 route_ui->midi_track()->write_immediate_event(
1158 note->on_event().size(), note->on_event().buffer());
1160 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1161 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1162 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1163 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1164 note_length_ms, G_PRIORITY_DEFAULT);
1168 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1170 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1173 route_ui->midi_track()->write_immediate_event(
1174 note->off_event().size(), note->off_event().buffer());
1180 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1182 const nframes64_t note_start_frames = beats_to_frames(note->time());
1184 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1185 (note_start_frames < _region->start());
1187 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1188 (note->note() <= midi_stream_view()->highest_note());
1194 MidiRegionView::update_note (CanvasNote* ev)
1196 boost::shared_ptr<NoteType> note = ev->note();
1198 const nframes64_t note_start_frames = beats_to_frames(note->time());
1199 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1201 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1202 const double y1 = midi_stream_view()->note_to_y(note->note());
1203 const double note_endpixel =
1204 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1206 ev->property_x1() = x;
1207 ev->property_y1() = y1;
1208 if (note->length() > 0) {
1209 ev->property_x2() = note_endpixel;
1211 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1213 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1215 if (note->length() == 0) {
1216 if (_active_notes) {
1217 assert(note->note() < 128);
1218 // If this note is already active there's a stuck note,
1219 // finish the old note rectangle
1220 if (_active_notes[note->note()]) {
1221 CanvasNote* const old_rect = _active_notes[note->note()];
1222 boost::shared_ptr<NoteType> old_note = old_rect->note();
1223 old_rect->property_x2() = x;
1224 old_rect->property_outline_what() = (guint32) 0xF;
1226 _active_notes[note->note()] = ev;
1228 /* outline all but right edge */
1229 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1231 /* outline all edges */
1232 ev->property_outline_what() = (guint32) 0xF;
1237 MidiRegionView::update_hit (CanvasHit* ev)
1239 boost::shared_ptr<NoteType> note = ev->note();
1241 const nframes64_t note_start_frames = beats_to_frames(note->time());
1242 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1243 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1244 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1249 /** Add a MIDI note to the view (with length).
1251 * If in sustained mode, notes with length 0 will be considered active
1252 * notes, and resolve_note should be called when the corresponding note off
1253 * event arrives, to properly display the note.
1256 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1258 CanvasNoteEvent* event = 0;
1260 assert(note->time() >= 0);
1261 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1263 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1265 if (midi_view()->note_mode() == Sustained) {
1267 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1269 update_note (ev_rect);
1273 MidiGhostRegion* gr;
1275 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1276 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1277 gr->add_note(ev_rect);
1281 } else if (midi_view()->note_mode() == Percussive) {
1283 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1285 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1287 update_hit (ev_diamond);
1296 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1297 note_selected(event, true);
1300 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1301 event->show_velocity();
1303 event->on_channel_selection_change(_last_channel_selection);
1304 _events.push_back(event);
1315 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1316 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1318 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1320 start_delta_command (_("step add"));
1321 delta_add_note (new_note, true, false);
1324 /* potentially extend region to hold new note */
1326 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1327 nframes64_t region_end = _region->position() + _region->length() - 1;
1329 if (end_frame > region_end) {
1330 _region->set_length (end_frame, this);
1337 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1339 assert(program.time >= 0);
1341 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1342 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1344 double height = midi_stream_view()->contents_height();
1346 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1347 new CanvasProgramChange(*this, *group,
1352 _custom_device_mode,
1353 program.time, program.channel, program.value));
1355 // Show unless program change is beyond the region bounds
1356 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1362 _pgm_changes.push_back(pgm_change);
1366 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1368 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1369 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1370 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1372 if (msb_control != 0) {
1373 msb = int(msb_control->get_float(true, time));
1374 cerr << "got msb " << msb;
1377 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1378 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1380 if (lsb_control != 0) {
1381 lsb = lsb_control->get_float(true, time);
1382 cerr << " got lsb " << lsb;
1385 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1386 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1387 float program_number = -1.0;
1388 if (program_control != 0) {
1389 program_number = program_control->get_float(true, time);
1390 cerr << " got program " << program_number << endl;
1393 key.msb = (int) floor(msb + 0.5);
1394 key.lsb = (int) floor(lsb + 0.5);
1395 key.program_number = (int) floor(program_number + 0.5);
1396 assert(key.is_sane());
1401 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1403 // TODO: Get the real event here and alter them at the original times
1404 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1405 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1406 if (msb_control != 0) {
1407 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1410 // TODO: Get the real event here and alter them at the original times
1411 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1412 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1413 if (lsb_control != 0) {
1414 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1417 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1418 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1420 assert(program_control != 0);
1421 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1427 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1429 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1430 alter_program_change(program_change_event, new_patch);
1434 MidiRegionView::previous_program(CanvasProgramChange& program)
1436 MIDI::Name::PatchPrimaryKey key;
1437 get_patch_key_at(program.event_time(), program.channel(), key);
1439 boost::shared_ptr<MIDI::Name::Patch> patch =
1440 MIDI::Name::MidiPatchManager::instance().previous_patch(
1442 _custom_device_mode,
1446 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1448 alter_program_change(program_change_event, patch->patch_primary_key());
1453 MidiRegionView::next_program(CanvasProgramChange& program)
1455 MIDI::Name::PatchPrimaryKey key;
1456 get_patch_key_at(program.event_time(), program.channel(), key);
1458 boost::shared_ptr<MIDI::Name::Patch> patch =
1459 MIDI::Name::MidiPatchManager::instance().next_patch(
1461 _custom_device_mode,
1465 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1467 alter_program_change(program_change_event, patch->patch_primary_key());
1472 MidiRegionView::delete_selection()
1474 if (_selection.empty()) {
1478 start_delta_command (_("delete selection"));
1480 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1481 if ((*i)->selected()) {
1482 _delta_command->remove((*i)->note());
1492 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1494 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1495 if ((*i)->selected() && (*i) != ev) {
1496 (*i)->selected(false);
1497 (*i)->hide_velocity();
1505 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1507 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1510 Selection::iterator tmp = i;
1513 (*i)->selected (false);
1514 _selection.erase (i);
1523 /* don't bother with removing this regionview from the editor selection,
1524 since we're about to add another note, and thus put/keep this
1525 regionview in the editor selection.
1528 if (!ev->selected()) {
1529 add_to_selection (ev);
1534 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1537 clear_selection_except(ev);
1542 if (!ev->selected()) {
1543 add_to_selection (ev);
1547 /* find end of latest note selected, select all between that and the start of "ev" */
1549 Evoral::MusicalTime earliest = DBL_MAX;
1550 Evoral::MusicalTime latest = 0;
1552 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1553 if ((*i)->note()->end_time() > latest) {
1554 latest = (*i)->note()->end_time();
1556 if ((*i)->note()->time() < earliest) {
1557 earliest = (*i)->note()->time();
1561 if (ev->note()->end_time() > latest) {
1562 latest = ev->note()->end_time();
1565 if (ev->note()->time() < earliest) {
1566 earliest = ev->note()->time();
1569 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1571 /* find notes entirely within OR spanning the earliest..latest range */
1573 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1574 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1575 add_to_selection (*i);
1579 /* if events were guaranteed to be time sorted, we could do this.
1580 but as of sept 10th 2009, they no longer are.
1583 if ((*i)->note()->time() > latest) {
1592 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1594 remove_from_selection (ev);
1598 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1608 // TODO: Make this faster by storing the last updated selection rect, and only
1609 // adjusting things that are in the area that appears/disappeared.
1610 // We probably need a tree to be able to find events in O(log(n)) time.
1612 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1614 /* check if any corner of the note is inside the rect
1617 1) this is computing "touched by", not "contained by" the rect.
1618 2) this does not require that events be sorted in time.
1621 const double ix1 = (*i)->x1();
1622 const double ix2 = (*i)->x2();
1623 const double iy1 = (*i)->y1();
1624 const double iy2 = (*i)->y2();
1626 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1627 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1628 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1629 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1632 if (!(*i)->selected()) {
1633 add_to_selection (*i);
1635 } else if ((*i)->selected()) {
1636 // Not inside rectangle
1637 remove_from_selection (*i);
1643 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1645 Selection::iterator i = _selection.find (ev);
1647 if (i != _selection.end()) {
1648 _selection.erase (i);
1651 ev->selected (false);
1652 ev->hide_velocity ();
1654 if (_selection.empty()) {
1655 PublicEditor& editor (trackview.editor());
1656 editor.get_selection().remove (this);
1661 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1663 bool add_mrv_selection = false;
1665 if (_selection.empty()) {
1666 add_mrv_selection = true;
1669 if (_selection.insert (ev).second) {
1670 ev->selected (true);
1671 play_midi_note ((ev)->note());
1674 if (add_mrv_selection) {
1675 PublicEditor& editor (trackview.editor());
1676 editor.get_selection().add (this);
1681 MidiRegionView::move_selection(double dx, double dy)
1683 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1684 (*i)->move_event(dx, dy);
1689 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1691 assert (!_selection.empty());
1693 uint8_t lowest_note_in_selection = 127;
1694 uint8_t highest_note_in_selection = 0;
1695 uint8_t highest_note_difference = 0;
1697 // find highest and lowest notes first
1699 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1700 uint8_t pitch = (*i)->note()->note();
1701 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1702 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1706 cerr << "dnote: " << (int) dnote << endl;
1707 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1708 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1709 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1710 << int(highest_note_in_selection) << endl;
1711 cerr << "selection size: " << _selection.size() << endl;
1712 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1715 // Make sure the note pitch does not exceed the MIDI standard range
1716 if (highest_note_in_selection + dnote > 127) {
1717 highest_note_difference = highest_note_in_selection - 127;
1720 start_diff_command(_("move notes"));
1722 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1724 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1727 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1729 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1732 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1738 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1740 uint8_t original_pitch = (*i)->note()->note();
1741 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1743 // keep notes in standard midi range
1744 clamp_to_0_127(new_pitch);
1746 // keep original pitch if note is dragged outside valid midi range
1747 if ((original_pitch != 0 && new_pitch == 0)
1748 || (original_pitch != 127 && new_pitch == 127)) {
1749 new_pitch = original_pitch;
1752 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1753 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1755 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1760 // care about notes being moved beyond the upper/lower bounds on the canvas
1761 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1762 highest_note_in_selection > midi_stream_view()->highest_note()) {
1763 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1768 MidiRegionView::snap_pixel_to_frame(double x)
1770 PublicEditor& editor = trackview.editor();
1771 // x is region relative, convert it to global absolute frames
1772 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1773 editor.snap_to(frame);
1774 return frame - _region->position(); // convert back to region relative
1778 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1780 PublicEditor& editor = trackview.editor();
1781 // x is region relative, convert it to global absolute frames
1782 nframes64_t frame = x + _region->position();
1783 editor.snap_to(frame);
1784 return frame - _region->position(); // convert back to region relative
1788 MidiRegionView::snap_to_pixel(double x)
1790 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1794 MidiRegionView::get_position_pixels()
1796 nframes64_t region_frame = get_position();
1797 return trackview.editor().frame_to_pixel(region_frame);
1801 MidiRegionView::get_end_position_pixels()
1803 nframes64_t frame = get_position() + get_duration ();
1804 return trackview.editor().frame_to_pixel(frame);
1808 MidiRegionView::beats_to_frames(double beats) const
1810 return _time_converter.to(beats);
1814 MidiRegionView::frames_to_beats(nframes64_t frames) const
1816 return _time_converter.from(frames);
1820 MidiRegionView::begin_resizing (bool /*at_front*/)
1822 _resize_data.clear();
1824 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1825 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1827 // only insert CanvasNotes into the map
1829 NoteResizeData *resize_data = new NoteResizeData();
1830 resize_data->canvas_note = note;
1832 // create a new SimpleRect from the note which will be the resize preview
1833 SimpleRect *resize_rect = new SimpleRect(
1834 *group, note->x1(), note->y1(), note->x2(), note->y2());
1836 // calculate the colors: get the color settings
1837 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1838 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1841 // make the resize preview notes more transparent and bright
1842 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1844 // calculate color based on note velocity
1845 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1846 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1850 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1851 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1853 resize_data->resize_rect = resize_rect;
1854 _resize_data.push_back(resize_data);
1860 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1862 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1863 SimpleRect* resize_rect = (*i)->resize_rect;
1864 CanvasNote* canvas_note = (*i)->canvas_note;
1869 current_x = canvas_note->x1() + delta_x;
1871 // x is in track relative, transform it to region relative
1872 current_x = delta_x - get_position_pixels();
1876 current_x = canvas_note->x2() + delta_x;
1878 // x is in track relative, transform it to region relative
1879 current_x = delta_x - get_end_position_pixels ();
1884 resize_rect->property_x1() = snap_to_pixel(current_x);
1885 resize_rect->property_x2() = canvas_note->x2();
1887 resize_rect->property_x2() = snap_to_pixel(current_x);
1888 resize_rect->property_x1() = canvas_note->x1();
1894 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1896 start_diff_command(_("resize notes"));
1898 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1899 CanvasNote* canvas_note = (*i)->canvas_note;
1900 SimpleRect* resize_rect = (*i)->resize_rect;
1901 const double region_start = get_position_pixels();
1906 current_x = canvas_note->x1() + delta_x;
1908 // x is in track relative, transform it to region relative
1909 current_x = region_start + delta_x;
1913 current_x = canvas_note->x2() + delta_x;
1915 // x is in track relative, transform it to region relative
1916 current_x = region_start + delta_x;
1920 current_x = snap_pixel_to_frame (current_x);
1921 current_x = frames_to_beats (current_x);
1923 if (at_front && current_x < canvas_note->note()->end_time()) {
1924 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1926 double len = canvas_note->note()->time() - current_x;
1927 len += canvas_note->note()->length();
1930 /* XXX convert to beats */
1931 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1936 double len = current_x - canvas_note->note()->time();
1939 /* XXX convert to beats */
1940 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1948 _resize_data.clear();
1953 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1955 uint8_t new_velocity;
1958 new_velocity = event->note()->velocity() + velocity;
1959 clamp_to_0_127(new_velocity);
1961 new_velocity = velocity;
1964 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1968 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1973 new_note = event->note()->note() + note;
1978 clamp_to_0_127 (new_note);
1979 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1983 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1985 bool change_start = false;
1986 bool change_length = false;
1987 Evoral::MusicalTime new_start;
1988 Evoral::MusicalTime new_length;
1990 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1992 front_delta: if positive - move the start of the note later in time (shortening it)
1993 if negative - move the start of the note earlier in time (lengthening it)
1995 end_delta: if positive - move the end of the note later in time (lengthening it)
1996 if negative - move the end of the note earlier in time (shortening it)
2000 if (front_delta < 0) {
2002 if (event->note()->time() < -front_delta) {
2005 new_start = event->note()->time() + front_delta; // moves earlier
2008 /* start moved toward zero, so move the end point out to where it used to be.
2009 Note that front_delta is negative, so this increases the length.
2012 new_length = event->note()->length() - front_delta;
2013 change_start = true;
2014 change_length = true;
2018 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2020 if (new_pos < event->note()->end_time()) {
2021 new_start = event->note()->time() + front_delta;
2022 /* start moved toward the end, so move the end point back to where it used to be */
2023 new_length = event->note()->length() - front_delta;
2024 change_start = true;
2025 change_length = true;
2032 bool can_change = true;
2033 if (end_delta < 0) {
2034 if (event->note()->length() < -end_delta) {
2040 new_length = event->note()->length() + end_delta;
2041 change_length = true;
2046 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2049 if (change_length) {
2050 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2055 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2057 Evoral::MusicalTime new_time;
2061 if (event->note()->time() < -delta) {
2064 new_time = event->note()->time() + delta;
2067 new_time = event->note()->time() + delta;
2073 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2077 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2081 if (_selection.empty()) {
2096 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2097 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2103 start_diff_command(_("change velocities"));
2105 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2106 Selection::iterator next = i;
2108 change_note_velocity (*i, delta, true);
2117 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2119 if (_selection.empty()) {
2136 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2138 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2142 if ((int8_t) (*i)->note()->note() + delta > 127) {
2149 start_diff_command (_("transpose"));
2151 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2152 Selection::iterator next = i;
2154 change_note_note (*i, delta, true);
2162 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2164 Evoral::MusicalTime delta;
2169 /* grab the current grid distance */
2171 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2173 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2174 cerr << "Grid type not available as beats - TO BE FIXED\n";
2183 start_diff_command (_("change note lengths"));
2185 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2186 Selection::iterator next = i;
2189 /* note the negation of the delta for start */
2191 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2200 MidiRegionView::nudge_notes (bool forward)
2202 if (_selection.empty()) {
2206 /* pick a note as the point along the timeline to get the nudge distance.
2207 its not necessarily the earliest note, so we may want to pull the notes out
2208 into a vector and sort before using the first one.
2211 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2213 nframes64_t distance;
2215 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2217 /* grid is off - use nudge distance */
2219 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2225 nframes64_t next_pos = ref_point;
2228 /* XXX need check on max_frames, but that needs max_frames64 or something */
2231 if (next_pos == 0) {
2237 cerr << "ref point was " << ref_point << " next was " << next_pos;
2238 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2239 distance = ref_point - next_pos;
2240 cerr << " final is " << next_pos << " distance = " << distance << endl;
2243 if (distance == 0) {
2247 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2253 start_diff_command (_("nudge"));
2255 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2256 Selection::iterator next = i;
2258 change_note_time (*i, delta, true);
2266 MidiRegionView::change_channel(uint8_t channel)
2268 start_diff_command(_("change channel"));
2269 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2270 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2277 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2279 if (_mouse_state == SelectTouchDragging) {
2280 note_selected(ev, true);
2284 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2285 // This causes an infinite loop on note add sometimes
2286 //PublicEditor& editor (trackview.editor());
2287 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2288 //editor.show_verbose_canvas_cursor_with (buf);
2292 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2294 PublicEditor& editor (trackview.editor());
2295 editor.hide_verbose_canvas_cursor ();
2300 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2302 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2304 display_model(msrc->model());
2308 MidiRegionView::set_frame_color()
2311 if (_selected && should_show_selection) {
2312 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2314 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2320 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2324 case FilterChannels:
2325 _force_channel = -1;
2328 _force_channel = mask;
2329 mask = 0xFFFF; // Show all notes as active (below)
2332 // Update notes for selection
2333 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2334 (*i)->on_channel_selection_change(mask);
2337 _last_channel_selection = mask;
2341 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2343 _model_name = model;
2344 _custom_device_mode = custom_device_mode;
2349 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2351 if (_selection.empty()) {
2355 PublicEditor& editor (trackview.editor());
2360 editor.get_cut_buffer().add (selection_as_cut_buffer());
2366 start_delta_command();
2368 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2373 delta_remove_note (*i);
2384 MidiRegionView::selection_as_cut_buffer () const
2388 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2389 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2392 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2399 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2405 start_delta_command (_("paste"));
2407 Evoral::MusicalTime beat_delta;
2408 Evoral::MusicalTime paste_pos_beats;
2409 Evoral::MusicalTime duration;
2410 Evoral::MusicalTime end_point;
2412 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2413 paste_pos_beats = frames_to_beats (pos - _region->position());
2414 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2415 paste_pos_beats = 0;
2417 _selection.clear ();
2419 for (int n = 0; n < (int) times; ++n) {
2421 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2423 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2424 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2426 /* make all newly added notes selected */
2428 delta_add_note (copied_note, true);
2429 end_point = copied_note->end_time();
2432 paste_pos_beats += duration;
2435 /* if we pasted past the current end of the region, extend the region */
2437 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2438 nframes64_t region_end = _region->position() + _region->length() - 1;
2440 if (end_frame > region_end) {
2442 trackview.session()->begin_reversible_command (_("paste"));
2444 XMLNode& before (_region->get_state());
2445 _region->set_length (end_frame, this);
2446 trackview.session()->add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2452 struct EventNoteTimeEarlyFirstComparator {
2453 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2454 return a->note()->time() < b->note()->time();
2459 MidiRegionView::time_sort_events ()
2461 if (!_sort_needed) {
2465 EventNoteTimeEarlyFirstComparator cmp;
2468 _sort_needed = false;
2472 MidiRegionView::goto_next_note ()
2474 // nframes64_t pos = -1;
2475 bool use_next = false;
2477 if (_events.back()->selected()) {
2481 time_sort_events ();
2483 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2484 if ((*i)->selected()) {
2487 } else if (use_next) {
2489 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2494 /* use the first one */
2496 unique_select (_events.front());
2501 MidiRegionView::goto_previous_note ()
2503 // nframes64_t pos = -1;
2504 bool use_next = false;
2506 if (_events.front()->selected()) {
2510 time_sort_events ();
2512 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2513 if ((*i)->selected()) {
2516 } else if (use_next) {
2518 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2523 /* use the last one */
2525 unique_select (*(_events.rbegin()));
2529 MidiRegionView::selection_as_notelist (Notes& selected)
2531 time_sort_events ();
2533 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2534 if ((*i)->selected()) {
2535 selected.insert ((*i)->note());