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"
41 #include "evoral/Parameter.hpp"
42 #include "evoral/Control.hpp"
44 #include "automation_region_view.h"
45 #include "automation_time_axis.h"
46 #include "canvas-hit.h"
47 #include "canvas-note.h"
48 #include "canvas-program-change.h"
49 #include "ghostregion.h"
50 #include "gui_thread.h"
52 #include "midi_cut_buffer.h"
53 #include "midi_list_editor.h"
54 #include "midi_region_view.h"
55 #include "midi_streamview.h"
56 #include "midi_time_axis.h"
57 #include "midi_time_axis.h"
58 #include "midi_util.h"
59 #include "public_editor.h"
60 #include "selection.h"
61 #include "simpleline.h"
62 #include "streamview.h"
68 using namespace ARDOUR;
70 using namespace Editing;
71 using namespace ArdourCanvas;
73 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
74 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
75 : RegionView (parent, tv, r, spu, basic_color)
77 , _last_channel_selection(0xFFFF)
78 , _default_note_length(1.0)
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 _note_group->raise_to_top();
95 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
96 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
97 TimeAxisViewItem::Visibility visibility)
98 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
100 , _last_channel_selection(0xFFFF)
101 , _default_note_length(1.0)
102 , _model_name(string())
103 , _custom_device_mode(string())
105 , _note_group(new ArdourCanvas::Group(*parent))
110 , _sort_needed (true)
111 , _optimization_iterator (_events.end())
114 _note_group->raise_to_top();
118 MidiRegionView::MidiRegionView (const MidiRegionView& other)
119 : sigc::trackable(other)
122 , _last_channel_selection(0xFFFF)
123 , _default_note_length(1.0)
124 , _model_name(string())
125 , _custom_device_mode(string())
127 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
132 , _sort_needed (true)
133 , _optimization_iterator (_events.end())
138 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
139 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
144 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
145 : RegionView (other, boost::shared_ptr<Region> (region))
147 , _last_channel_selection(0xFFFF)
148 , _default_note_length(1.0)
149 , _model_name(string())
150 , _custom_device_mode(string())
152 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
157 , _sort_needed (true)
158 , _optimization_iterator (_events.end())
163 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
164 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
170 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
173 midi_region()->midi_source(0)->load_model();
176 _model = midi_region()->midi_source(0)->model();
177 _enable_display = false;
179 RegionView::init (basic_color, false);
181 compute_colors (basic_color);
183 set_height (trackview.current_height());
186 region_sync_changed ();
187 region_resized (BoundsChanged);
190 reset_width_dependent_items (_pixel_width);
194 _enable_display = true;
197 display_model (_model);
201 group->raise_to_top();
202 group->signal_event().connect (mem_fun (this, &MidiRegionView::canvas_event), false);
204 midi_view()->signal_channel_mode_changed().connect(
205 mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
207 midi_view()->signal_midi_patch_settings_changed().connect(
208 mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
212 MidiRegionView::canvas_event(GdkEvent* ev)
214 PublicEditor& editor (trackview.editor());
216 if (!editor.internal_editing()) {
220 static double drag_start_x, drag_start_y;
221 static double last_x, last_y;
222 double event_x, event_y;
223 nframes64_t event_frame = 0;
226 static ArdourCanvas::SimpleRect* drag_rect = 0;
228 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
229 to its items, which means that ev->type == GDK_SCROLL will never be seen
234 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
236 if (ev->scroll.direction == GDK_SCROLL_UP) {
237 change_velocities (true, fine, false);
239 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
240 change_velocities (false, fine, false);
249 /* since GTK bindings are generally activated on press, and since
250 detectable auto-repeat is the name of the game and only sends
251 repeated presses, carry out key actions at key press, not release.
254 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
255 _mouse_state = SelectTouchDragging;
258 } else if (ev->key.keyval == GDK_Escape) {
262 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
264 bool start = (ev->key.keyval == GDK_comma);
265 bool end = (ev->key.keyval == GDK_period);
266 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
267 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
269 change_note_lengths (fine, shorter, start, end);
273 } else if (ev->key.keyval == GDK_Delete) {
278 } else if (ev->key.keyval == GDK_Tab) {
280 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
281 goto_previous_note ();
287 } else if (ev->key.keyval == GDK_Up) {
289 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
290 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
292 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
293 change_velocities (true, fine, allow_smush);
295 transpose (true, fine, allow_smush);
299 } else if (ev->key.keyval == GDK_Down) {
301 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
302 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
304 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
305 change_velocities (false, fine, allow_smush);
307 transpose (false, fine, allow_smush);
311 } else if (ev->key.keyval == GDK_Left) {
316 } else if (ev->key.keyval == GDK_Right) {
321 } else if (ev->key.keyval == GDK_Control_L) {
324 } else if (ev->key.keyval == GDK_r) {
325 /* if we're not step editing, this really doesn't matter */
326 midi_view()->step_edit_rest ();
332 case GDK_KEY_RELEASE:
333 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
339 case GDK_BUTTON_PRESS:
340 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
341 _pressed_button = ev->button.button;
342 _mouse_state = Pressed;
345 _pressed_button = ev->button.button;
348 case GDK_2BUTTON_PRESS:
351 case GDK_ENTER_NOTIFY:
352 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
353 Keyboard::magic_widget_grab_focus();
357 case GDK_MOTION_NOTIFY:
358 event_x = ev->motion.x;
359 event_y = ev->motion.y;
360 group->w2i(event_x, event_y);
362 // convert event_x to global frame
363 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
364 trackview.editor().snap_to(event_frame);
365 // convert event_frame back to local coordinates relative to position
366 event_frame -= _region->position();
368 switch (_mouse_state) {
369 case Pressed: // Drag start
372 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
373 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
374 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
377 drag_start_x = event_x;
378 drag_start_y = event_y;
380 drag_rect = new ArdourCanvas::SimpleRect(*group);
381 drag_rect->property_x1() = event_x;
382 drag_rect->property_y1() = event_y;
383 drag_rect->property_x2() = event_x;
384 drag_rect->property_y2() = event_y;
385 drag_rect->property_outline_what() = 0xFF;
386 drag_rect->property_outline_color_rgba()
387 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
388 drag_rect->property_fill_color_rgba()
389 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
391 _mouse_state = SelectRectDragging;
394 // Add note drag start
395 } else if (editor.current_mouse_mode() == MouseRange) {
396 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
397 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
400 drag_start_x = event_x;
401 drag_start_y = event_y;
403 drag_rect = new ArdourCanvas::SimpleRect(*group);
404 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
406 drag_rect->property_y1() = midi_stream_view()->note_to_y(
407 midi_stream_view()->y_to_note(event_y));
408 drag_rect->property_x2() = event_x;
409 drag_rect->property_y2() = drag_rect->property_y1()
410 + floor(midi_stream_view()->note_height());
411 drag_rect->property_outline_what() = 0xFF;
412 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
413 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
415 _mouse_state = AddDragging;
421 case SelectRectDragging: // Select drag motion
422 case AddDragging: // Add note drag motion
423 if (ev->motion.is_hint) {
426 GdkModifierType state;
427 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
432 if (_mouse_state == AddDragging)
433 event_x = trackview.editor().frame_to_pixel(event_frame);
436 if (event_x > drag_start_x)
437 drag_rect->property_x2() = event_x;
439 drag_rect->property_x1() = event_x;
442 if (drag_rect && _mouse_state == SelectRectDragging) {
443 if (event_y > drag_start_y)
444 drag_rect->property_y2() = event_y;
446 drag_rect->property_y1() = event_y;
448 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
454 case SelectTouchDragging:
462 case GDK_BUTTON_RELEASE:
463 event_x = ev->motion.x;
464 event_y = ev->motion.y;
465 group->w2i(event_x, event_y);
466 group->ungrab(ev->button.time);
467 event_frame = trackview.editor().pixel_to_frame(event_x);
469 if (ev->button.button == 3) {
471 } else if (_pressed_button != 1) {
475 switch (_mouse_state) {
476 case Pressed: // Clicked
477 switch (editor.current_mouse_mode()) {
483 create_note_at(event_x, event_y, _default_note_length);
490 case SelectRectDragging: // Select drag done
495 case AddDragging: // Add drag done
497 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
498 const double x = drag_rect->property_x1();
499 const double length = trackview.editor().pixel_to_frame(
500 drag_rect->property_x2() - drag_rect->property_x1());
502 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
517 MidiRegionView::show_list_editor ()
519 MidiListEditor* mle = new MidiListEditor (trackview.session(), midi_region());
523 /** Add a note to the model, and the view, at a canvas (click) coordinate.
524 * \param x horizontal position in pixels
525 * \param y vertical position in pixels
526 * \param length duration of the note in beats */
528 MidiRegionView::create_note_at(double x, double y, double length)
530 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
531 MidiStreamView* const view = mtv->midi_view();
533 double note = midi_stream_view()->y_to_note(y);
536 assert(note <= 127.0);
538 // Start of note in frames relative to region start
539 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
540 assert(start_frames >= 0);
543 length = frames_to_beats(
544 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
546 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
547 frames_to_beats(start_frames + _region->start()), length,
548 (uint8_t)note, 0x40));
550 view->update_note_range(new_note->note());
552 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
554 _model->apply_command(trackview.session(), cmd);
556 play_midi_note (new_note);
560 MidiRegionView::clear_events()
565 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
566 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
571 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
576 _pgm_changes.clear();
578 _optimization_iterator = _events.end();
583 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
586 content_connection.disconnect ();
587 content_connection = _model->ContentsChanged.connect(sigc::mem_fun(this, &MidiRegionView::redisplay_model));
590 if (_enable_display) {
597 MidiRegionView::start_delta_command(string name)
599 if (!_delta_command) {
600 _delta_command = _model->new_delta_command(name);
605 MidiRegionView::start_diff_command(string name)
607 if (!_diff_command) {
608 _diff_command = _model->new_diff_command(name);
613 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
615 if (_delta_command) {
616 _delta_command->add(note);
619 _marked_for_selection.insert(note);
622 _marked_for_velocity.insert(note);
627 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
629 if (_delta_command && ev->note()) {
630 _delta_command->remove(ev->note());
635 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
636 MidiModel::DiffCommand::Property property,
640 _diff_command->change (ev->note(), property, val);
645 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
646 MidiModel::DiffCommand::Property property,
647 Evoral::MusicalTime val)
650 _diff_command->change (ev->note(), property, val);
655 MidiRegionView::apply_delta()
657 if (!_delta_command) {
661 // Mark all selected notes for selection when model reloads
662 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
663 _marked_for_selection.insert((*i)->note());
666 _model->apply_command(trackview.session(), _delta_command);
668 midi_view()->midi_track()->diskstream()->playlist_modified();
670 _marked_for_selection.clear();
671 _marked_for_velocity.clear();
675 MidiRegionView::apply_diff ()
677 if (!_diff_command) {
681 _model->apply_command(trackview.session(), _diff_command);
683 midi_view()->midi_track()->diskstream()->playlist_modified();
685 _marked_for_velocity.clear();
689 MidiRegionView::apply_delta_as_subcommand()
691 if (!_delta_command) {
695 // Mark all selected notes for selection when model reloads
696 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
697 _marked_for_selection.insert((*i)->note());
700 _model->apply_command_as_subcommand(trackview.session(), _delta_command);
702 midi_view()->midi_track()->diskstream()->playlist_modified();
704 _marked_for_selection.clear();
705 _marked_for_velocity.clear();
709 MidiRegionView::apply_diff_as_subcommand()
711 if (!_diff_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(), _diff_command);
722 midi_view()->midi_track()->diskstream()->playlist_modified();
724 _marked_for_selection.clear();
725 _marked_for_velocity.clear();
729 MidiRegionView::abort_command()
731 delete _delta_command;
733 delete _diff_command;
739 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
741 if (_optimization_iterator != _events.end()) {
742 ++_optimization_iterator;
745 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
746 return *_optimization_iterator;
749 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
750 if ((*_optimization_iterator)->note() == note) {
751 return *_optimization_iterator;
759 MidiRegionView::redisplay_model()
761 // Don't redisplay the model if we're currently recording and displaying that
767 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
771 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
777 MidiModel::Notes& notes (_model->notes());
778 _optimization_iterator = _events.begin();
780 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
782 boost::shared_ptr<NoteType> note (*n);
783 CanvasNoteEvent* cne;
786 if (note_in_region_range (note, visible)) {
788 if ((cne = find_canvas_note (note)) != 0) {
795 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
797 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
809 add_note (note, visible);
814 if ((cne = find_canvas_note (note)) != 0) {
821 /* remove note items that are no longer valid */
823 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
824 if (!(*i)->valid ()) {
826 i = _events.erase (i);
833 display_program_changes();
835 _model->read_unlock();
837 _marked_for_selection.clear ();
838 _marked_for_velocity.clear ();
840 /* we may have caused _events to contain things out of order (e.g. if a note
841 moved earlier or later). we don't generally need them in time order, but
842 make a note that a sort is required for those cases that require it.
849 MidiRegionView::display_program_changes()
851 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
856 Glib::Mutex::Lock lock (control->list()->lock());
858 uint8_t channel = control->parameter().channel();
860 for (AutomationList::const_iterator event = control->list()->begin();
861 event != control->list()->end(); ++event) {
862 double event_time = (*event)->when;
863 double program_number = floor((*event)->value + 0.5);
865 // Get current value of bank select MSB at time of the program change
866 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
867 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
869 if (msb_control != 0) {
870 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
873 // Get current value of bank select LSB at time of the program change
874 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
875 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
877 if (lsb_control != 0) {
878 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
881 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
883 boost::shared_ptr<MIDI::Name::Patch> patch =
884 MIDI::Name::MidiPatchManager::instance().find_patch(
885 _model_name, _custom_device_mode, channel, patch_key);
887 PCEvent program_change(event_time, uint8_t(program_number), channel);
890 add_pgm_change(program_change, patch->name());
893 snprintf(buf, 4, "%d", int(program_number));
894 add_pgm_change(program_change, buf);
900 MidiRegionView::display_sysexes()
902 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
903 Evoral::MusicalTime time = (*i)->time();
908 for (uint32_t b = 0; b < (*i)->size(); ++b) {
909 str << int((*i)->buffer()[b]);
910 if (b != (*i)->size() -1) {
914 string text = str.str();
916 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
918 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
920 double height = midi_stream_view()->contents_height();
922 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
923 new CanvasSysEx(*this, *group, text, height, x, 1.0));
925 // Show unless program change is beyond the region bounds
926 if (time - _region->start() >= _region->length() || time < _region->start()) {
932 _sys_exes.push_back(sysex);
937 MidiRegionView::~MidiRegionView ()
939 in_destructor = true;
941 RegionViewGoingAway (this); /* EMIT_SIGNAL */
950 delete _delta_command;
954 MidiRegionView::region_resized (Change what_changed)
956 RegionView::region_resized(what_changed);
958 if (what_changed & ARDOUR::PositionChanged) {
959 set_duration(_region->length(), 0);
960 if (_enable_display) {
967 MidiRegionView::reset_width_dependent_items (double pixel_width)
969 RegionView::reset_width_dependent_items(pixel_width);
970 assert(_pixel_width == pixel_width);
972 if (_enable_display) {
978 MidiRegionView::set_height (double height)
980 static const double FUDGE = 2.0;
981 const double old_height = _height;
982 RegionView::set_height(height);
983 _height = height - FUDGE;
985 apply_note_range(midi_stream_view()->lowest_note(),
986 midi_stream_view()->highest_note(),
987 height != old_height + FUDGE);
990 name_pixbuf->raise_to_top();
995 /** Apply the current note range from the stream view
996 * by repositioning/hiding notes as necessary
999 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1001 if (!_enable_display) {
1005 if (!force && _current_range_min == min && _current_range_max == max) {
1009 _current_range_min = min;
1010 _current_range_max = max;
1012 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1013 CanvasNoteEvent* event = *i;
1014 boost::shared_ptr<NoteType> note (event->note());
1016 if (note->note() < _current_range_min ||
1017 note->note() > _current_range_max) {
1023 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1025 const double y1 = midi_stream_view()->note_to_y(note->note());
1026 const double y2 = y1 + floor(midi_stream_view()->note_height());
1028 cnote->property_y1() = y1;
1029 cnote->property_y2() = y2;
1031 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1033 double x = trackview.editor().frame_to_pixel(
1034 beats_to_frames(note->time()) - _region->start());
1035 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1036 double y = midi_stream_view()->note_to_y(event->note()->note())
1037 + ((diamond_size-2.0) / 4.0);
1039 chit->set_height (diamond_size);
1040 chit->move (x - chit->x1(), y - chit->y1());
1047 MidiRegionView::add_ghost (TimeAxisView& tv)
1051 double unit_position = _region->position () / samples_per_unit;
1052 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1053 MidiGhostRegion* ghost;
1055 if (mtv && mtv->midi_view()) {
1056 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1057 to allow having midi notes on top of note lines and waveforms.
1059 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1061 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1064 ghost->set_height ();
1065 ghost->set_duration (_region->length() / samples_per_unit);
1066 ghosts.push_back (ghost);
1068 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1069 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1070 ghost->add_note(note);
1074 ghost->GoingAway.connect (mem_fun(*this, &MidiRegionView::remove_ghost));
1080 /** Begin tracking note state for successive calls to add_event
1083 MidiRegionView::begin_write()
1085 assert(!_active_notes);
1086 _active_notes = new CanvasNote*[128];
1087 for (unsigned i=0; i < 128; ++i) {
1088 _active_notes[i] = 0;
1093 /** Destroy note state for add_event
1096 MidiRegionView::end_write()
1098 delete[] _active_notes;
1100 _marked_for_selection.clear();
1101 _marked_for_velocity.clear();
1105 /** Resolve an active MIDI note (while recording).
1108 MidiRegionView::resolve_note(uint8_t note, double end_time)
1110 if (midi_view()->note_mode() != Sustained) {
1114 if (_active_notes && _active_notes[note]) {
1115 const nframes64_t end_time_frames = beats_to_frames(end_time);
1116 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1117 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1118 _active_notes[note] = 0;
1123 /** Extend active notes to rightmost edge of region (if length is changed)
1126 MidiRegionView::extend_active_notes()
1128 if (!_active_notes) {
1132 for (unsigned i=0; i < 128; ++i) {
1133 if (_active_notes[i]) {
1134 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1140 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1142 if (!trackview.editor().sound_notes()) {
1146 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1149 route_ui->midi_track()->write_immediate_event(
1150 note->on_event().size(), note->on_event().buffer());
1152 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1153 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1154 * (1000 / (double)route_ui->session().nominal_frame_rate());
1155 Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1156 note_length_ms, G_PRIORITY_DEFAULT);
1160 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1162 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1165 route_ui->midi_track()->write_immediate_event(
1166 note->off_event().size(), note->off_event().buffer());
1172 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1174 const nframes64_t note_start_frames = beats_to_frames(note->time());
1176 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1177 (note_start_frames < _region->start());
1179 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1180 (note->note() <= midi_stream_view()->highest_note());
1186 MidiRegionView::update_note (CanvasNote* ev)
1188 boost::shared_ptr<NoteType> note = ev->note();
1190 const nframes64_t note_start_frames = beats_to_frames(note->time());
1191 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1193 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1196 const double y1 = midi_stream_view()->note_to_y(note->note());
1197 const double note_endpixel =
1198 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1200 ev->property_x1() = x;
1201 ev->property_y1() = y1;
1202 if (note->length() > 0) {
1203 ev->property_x2() = note_endpixel;
1205 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1207 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1209 if (note->length() == 0) {
1210 if (_active_notes) {
1211 assert(note->note() < 128);
1212 // If this note is already active there's a stuck note,
1213 // finish the old note rectangle
1214 if (_active_notes[note->note()]) {
1215 CanvasNote* const old_rect = _active_notes[note->note()];
1216 boost::shared_ptr<NoteType> old_note = old_rect->note();
1217 old_rect->property_x2() = x;
1218 old_rect->property_outline_what() = (guint32) 0xF;
1220 _active_notes[note->note()] = ev;
1222 /* outline all but right edge */
1223 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1225 /* outline all edges */
1226 ev->property_outline_what() = (guint32) 0xF;
1231 MidiRegionView::update_hit (CanvasHit* ev)
1233 boost::shared_ptr<NoteType> note = ev->note();
1235 const nframes64_t note_start_frames = beats_to_frames(note->time());
1236 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1237 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1238 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1243 /** Add a MIDI note to the view (with length).
1245 * If in sustained mode, notes with length 0 will be considered active
1246 * notes, and resolve_note should be called when the corresponding note off
1247 * event arrives, to properly display the note.
1250 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1252 CanvasNoteEvent* event = 0;
1254 assert(note->time() >= 0);
1255 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1257 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1259 if (midi_view()->note_mode() == Sustained) {
1261 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1263 update_note (ev_rect);
1267 MidiGhostRegion* gr;
1269 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1270 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1271 gr->add_note(ev_rect);
1275 } else if (midi_view()->note_mode() == Percussive) {
1277 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1279 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1281 update_hit (ev_diamond);
1290 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1291 note_selected(event, true);
1294 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1295 event->show_velocity();
1297 event->on_channel_selection_change(_last_channel_selection);
1298 _events.push_back(event);
1309 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1310 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1312 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1314 start_delta_command (_("step add"));
1315 delta_add_note (new_note, true, false);
1318 /* potentially extend region to hold new note */
1320 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1321 nframes64_t region_end = _region->position() + _region->length() - 1;
1323 if (end_frame > region_end) {
1324 _region->set_length (end_frame, this);
1331 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1333 assert(program.time >= 0);
1335 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1336 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1338 double height = midi_stream_view()->contents_height();
1340 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1341 new CanvasProgramChange(*this, *group,
1346 _custom_device_mode,
1347 program.time, program.channel, program.value));
1349 // Show unless program change is beyond the region bounds
1350 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1356 _pgm_changes.push_back(pgm_change);
1360 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1362 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1363 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1364 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1366 if (msb_control != 0) {
1367 msb = int(msb_control->get_float(true, time));
1368 cerr << "got msb " << msb;
1371 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1372 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1374 if (lsb_control != 0) {
1375 lsb = lsb_control->get_float(true, time);
1376 cerr << " got lsb " << lsb;
1379 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1380 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1381 float program_number = -1.0;
1382 if (program_control != 0) {
1383 program_number = program_control->get_float(true, time);
1384 cerr << " got program " << program_number << endl;
1387 key.msb = (int) floor(msb + 0.5);
1388 key.lsb = (int) floor(lsb + 0.5);
1389 key.program_number = (int) floor(program_number + 0.5);
1390 assert(key.is_sane());
1395 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1397 // TODO: Get the real event here and alter them at the original times
1398 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1399 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1400 if (msb_control != 0) {
1401 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1404 // TODO: Get the real event here and alter them at the original times
1405 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1406 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1407 if (lsb_control != 0) {
1408 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1411 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1412 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1414 assert(program_control != 0);
1415 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1421 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1423 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1424 alter_program_change(program_change_event, new_patch);
1428 MidiRegionView::previous_program(CanvasProgramChange& program)
1430 MIDI::Name::PatchPrimaryKey key;
1431 get_patch_key_at(program.event_time(), program.channel(), key);
1433 boost::shared_ptr<MIDI::Name::Patch> patch =
1434 MIDI::Name::MidiPatchManager::instance().previous_patch(
1436 _custom_device_mode,
1440 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1442 alter_program_change(program_change_event, patch->patch_primary_key());
1447 MidiRegionView::next_program(CanvasProgramChange& program)
1449 MIDI::Name::PatchPrimaryKey key;
1450 get_patch_key_at(program.event_time(), program.channel(), key);
1452 boost::shared_ptr<MIDI::Name::Patch> patch =
1453 MIDI::Name::MidiPatchManager::instance().next_patch(
1455 _custom_device_mode,
1459 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1461 alter_program_change(program_change_event, patch->patch_primary_key());
1466 MidiRegionView::delete_selection()
1468 if (_selection.empty()) {
1472 start_delta_command (_("delete selection"));
1474 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1475 if ((*i)->selected()) {
1476 _delta_command->remove((*i)->note());
1486 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1488 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1489 if ((*i)->selected() && (*i) != ev) {
1490 (*i)->selected(false);
1491 (*i)->hide_velocity();
1499 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1501 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1504 Selection::iterator tmp = i;
1507 (*i)->selected (false);
1508 _selection.erase (i);
1517 /* don't bother with removing this regionview from the editor selection,
1518 since we're about to add another note, and thus put/keep this
1519 regionview in the editor selection.
1522 if (!ev->selected()) {
1523 add_to_selection (ev);
1528 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1531 clear_selection_except(ev);
1536 if (!ev->selected()) {
1537 add_to_selection (ev);
1541 /* find end of latest note selected, select all between that and the start of "ev" */
1543 Evoral::MusicalTime earliest = DBL_MAX;
1544 Evoral::MusicalTime latest = 0;
1546 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1547 if ((*i)->note()->end_time() > latest) {
1548 latest = (*i)->note()->end_time();
1550 if ((*i)->note()->time() < earliest) {
1551 earliest = (*i)->note()->time();
1555 if (ev->note()->end_time() > latest) {
1556 latest = ev->note()->end_time();
1559 if (ev->note()->time() < earliest) {
1560 earliest = ev->note()->time();
1563 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1565 /* find notes entirely within OR spanning the earliest..latest range */
1567 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1568 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1569 add_to_selection (*i);
1573 /* if events were guaranteed to be time sorted, we could do this.
1574 but as of sept 10th 2009, they no longer are.
1577 if ((*i)->note()->time() > latest) {
1586 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1588 remove_from_selection (ev);
1592 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1602 // TODO: Make this faster by storing the last updated selection rect, and only
1603 // adjusting things that are in the area that appears/disappeared.
1604 // We probably need a tree to be able to find events in O(log(n)) time.
1606 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1608 /* check if any corner of the note is inside the rect
1611 1) this is computing "touched by", not "contained by" the rect.
1612 2) this does not require that events be sorted in time.
1615 const double ix1 = (*i)->x1();
1616 const double ix2 = (*i)->x2();
1617 const double iy1 = (*i)->y1();
1618 const double iy2 = (*i)->y2();
1620 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1621 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1622 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1623 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1626 if (!(*i)->selected()) {
1627 add_to_selection (*i);
1629 } else if ((*i)->selected()) {
1630 // Not inside rectangle
1631 remove_from_selection (*i);
1637 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1639 Selection::iterator i = _selection.find (ev);
1641 if (i != _selection.end()) {
1642 _selection.erase (i);
1645 ev->selected (false);
1646 ev->hide_velocity ();
1648 if (_selection.empty()) {
1649 PublicEditor& editor (trackview.editor());
1650 editor.get_selection().remove (this);
1655 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1657 bool add_mrv_selection = false;
1659 if (_selection.empty()) {
1660 add_mrv_selection = true;
1663 if (_selection.insert (ev).second) {
1664 ev->selected (true);
1665 play_midi_note ((ev)->note());
1668 if (add_mrv_selection) {
1669 PublicEditor& editor (trackview.editor());
1670 editor.get_selection().add (this);
1675 MidiRegionView::move_selection(double dx, double dy)
1677 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1678 (*i)->move_event(dx, dy);
1683 MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
1685 // TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy...
1686 if (_selection.find(ev) == _selection.end()) {
1690 uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note();
1691 uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
1692 uint8_t highest_note_difference = 0;
1694 // find highest and lowest notes first
1695 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1696 uint8_t pitch = (*i)->note()->note();
1697 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1698 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1702 cerr << "dnote: " << (int) dnote << endl;
1703 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1704 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1705 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1706 << int(highest_note_in_selection) << endl;
1707 cerr << "selection size: " << _selection.size() << endl;
1708 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1711 // Make sure the note pitch does not exceed the MIDI standard range
1712 if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
1713 highest_note_difference = highest_note_in_selection - 127;
1716 start_diff_command(_("move notes"));
1718 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1720 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1723 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1725 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1728 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1734 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1736 uint8_t original_pitch = (*i)->note()->note();
1737 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1739 // keep notes in standard midi range
1740 clamp_to_0_127(new_pitch);
1742 // keep original pitch if note is dragged outside valid midi range
1743 if ((original_pitch != 0 && new_pitch == 0)
1744 || (original_pitch != 127 && new_pitch == 127)) {
1745 new_pitch = original_pitch;
1748 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1749 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1751 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1756 // care about notes being moved beyond the upper/lower bounds on the canvas
1757 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1758 highest_note_in_selection > midi_stream_view()->highest_note()) {
1759 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1764 MidiRegionView::snap_pixel_to_frame(double x)
1766 PublicEditor& editor = trackview.editor();
1767 // x is region relative, convert it to global absolute frames
1768 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1769 editor.snap_to(frame);
1770 return frame - _region->position(); // convert back to region relative
1774 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1776 PublicEditor& editor = trackview.editor();
1777 // x is region relative, convert it to global absolute frames
1778 nframes64_t frame = x + _region->position();
1779 editor.snap_to(frame);
1780 return frame - _region->position(); // convert back to region relative
1784 MidiRegionView::snap_to_pixel(double x)
1786 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1790 MidiRegionView::get_position_pixels()
1792 nframes64_t region_frame = get_position();
1793 return trackview.editor().frame_to_pixel(region_frame);
1797 MidiRegionView::get_end_position_pixels()
1799 nframes64_t frame = get_position() + get_duration ();
1800 return trackview.editor().frame_to_pixel(frame);
1804 MidiRegionView::beats_to_frames(double beats) const
1806 return _time_converter.to(beats);
1810 MidiRegionView::frames_to_beats(nframes64_t frames) const
1812 return _time_converter.from(frames);
1816 MidiRegionView::begin_resizing (bool /*at_front*/)
1818 _resize_data.clear();
1820 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1821 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1823 // only insert CanvasNotes into the map
1825 NoteResizeData *resize_data = new NoteResizeData();
1826 resize_data->canvas_note = note;
1828 // create a new SimpleRect from the note which will be the resize preview
1829 SimpleRect *resize_rect = new SimpleRect(
1830 *group, note->x1(), note->y1(), note->x2(), note->y2());
1832 // calculate the colors: get the color settings
1833 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1834 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1837 // make the resize preview notes more transparent and bright
1838 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1840 // calculate color based on note velocity
1841 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1842 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1846 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1847 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1849 resize_data->resize_rect = resize_rect;
1850 _resize_data.push_back(resize_data);
1856 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1858 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1859 SimpleRect* resize_rect = (*i)->resize_rect;
1860 CanvasNote* canvas_note = (*i)->canvas_note;
1865 current_x = canvas_note->x1() + delta_x;
1867 // x is in track relative, transform it to region relative
1868 current_x = delta_x - get_position_pixels();
1872 current_x = canvas_note->x2() + delta_x;
1874 // x is in track relative, transform it to region relative
1875 current_x = delta_x - get_end_position_pixels ();
1880 resize_rect->property_x1() = snap_to_pixel(current_x);
1881 resize_rect->property_x2() = canvas_note->x2();
1883 resize_rect->property_x2() = snap_to_pixel(current_x);
1884 resize_rect->property_x1() = canvas_note->x1();
1890 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1892 start_diff_command(_("resize notes"));
1894 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1895 CanvasNote* canvas_note = (*i)->canvas_note;
1896 SimpleRect* resize_rect = (*i)->resize_rect;
1897 const double region_start = get_position_pixels();
1902 current_x = canvas_note->x1() + delta_x;
1904 // x is in track relative, transform it to region relative
1905 current_x = region_start + delta_x;
1909 current_x = canvas_note->x2() + delta_x;
1911 // x is in track relative, transform it to region relative
1912 current_x = region_start + delta_x;
1916 current_x = snap_pixel_to_frame (current_x);
1917 current_x = frames_to_beats (current_x);
1919 if (at_front && current_x < canvas_note->note()->end_time()) {
1920 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1924 double len = current_x - canvas_note->note()->time();
1927 /* XXX convert to beats */
1928 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1936 _resize_data.clear();
1941 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1943 uint8_t new_velocity;
1946 new_velocity = event->note()->velocity() + velocity;
1947 clamp_to_0_127(new_velocity);
1949 new_velocity = velocity;
1952 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1956 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1961 new_note = event->note()->note() + note;
1966 clamp_to_0_127 (new_note);
1967 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1971 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1973 bool change_start = false;
1974 bool change_length = false;
1975 Evoral::MusicalTime new_start;
1976 Evoral::MusicalTime new_length;
1978 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1980 front_delta: if positive - move the start of the note later in time (shortening it)
1981 if negative - move the start of the note earlier in time (lengthening it)
1983 end_delta: if positive - move the end of the note later in time (lengthening it)
1984 if negative - move the end of the note earlier in time (shortening it)
1988 if (front_delta < 0) {
1990 if (event->note()->time() < -front_delta) {
1993 new_start = event->note()->time() + front_delta; // moves earlier
1996 /* start moved toward zero, so move the end point out to where it used to be.
1997 Note that front_delta is negative, so this increases the length.
2000 new_length = event->note()->length() - front_delta;
2001 change_start = true;
2002 change_length = true;
2006 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2008 if (new_pos < event->note()->end_time()) {
2009 new_start = event->note()->time() + front_delta;
2010 /* start moved toward the end, so move the end point back to where it used to be */
2011 new_length = event->note()->length() - front_delta;
2012 change_start = true;
2013 change_length = true;
2020 bool can_change = true;
2021 if (end_delta < 0) {
2022 if (event->note()->length() < -end_delta) {
2028 new_length = event->note()->length() + end_delta;
2029 change_length = true;
2034 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2037 if (change_length) {
2038 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2043 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2045 Evoral::MusicalTime new_time;
2049 if (event->note()->time() < -delta) {
2052 new_time = event->note()->time() + delta;
2055 new_time = event->note()->time() + delta;
2061 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2065 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2069 if (_selection.empty()) {
2084 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2085 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2091 start_diff_command(_("change velocities"));
2093 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2094 Selection::iterator next = i;
2096 change_note_velocity (*i, delta, true);
2105 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2107 if (_selection.empty()) {
2124 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2126 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2130 if ((int8_t) (*i)->note()->note() + delta > 127) {
2137 start_diff_command (_("transpose"));
2139 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2140 Selection::iterator next = i;
2142 change_note_note (*i, delta, true);
2150 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2152 Evoral::MusicalTime delta;
2157 /* grab the current grid distance */
2159 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2161 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2162 cerr << "Grid type not available as beats - TO BE FIXED\n";
2171 start_diff_command (_("change note lengths"));
2173 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2174 Selection::iterator next = i;
2177 /* note the negation of the delta for start */
2179 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2188 MidiRegionView::nudge_notes (bool forward)
2190 if (_selection.empty()) {
2194 /* pick a note as the point along the timeline to get the nudge distance.
2195 its not necessarily the earliest note, so we may want to pull the notes out
2196 into a vector and sort before using the first one.
2199 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2201 nframes64_t distance;
2203 if ((distance = trackview.editor().get_nudge_distance (ref_point, unused)) == 0) {
2205 /* no nudge distance set - use grid */
2207 nframes64_t next_pos = ref_point;
2210 /* XXX need check on max_frames, but that needs max_frames64 or something */
2213 if (next_pos == 0) {
2219 cerr << "ref point was " << ref_point << " next was " << next_pos;
2220 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2221 distance = ref_point - next_pos;
2222 cerr << " final is " << next_pos << " distance = " << distance << endl;
2225 if (distance == 0) {
2229 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2235 start_diff_command (_("nudge"));
2237 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2238 Selection::iterator next = i;
2240 change_note_time (*i, delta, true);
2248 MidiRegionView::change_channel(uint8_t channel)
2250 start_diff_command(_("change channel"));
2251 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2252 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2259 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2261 if (_mouse_state == SelectTouchDragging) {
2262 note_selected(ev, true);
2265 PublicEditor& editor (trackview.editor());
2266 editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2270 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2272 PublicEditor& editor (trackview.editor());
2273 editor.hide_verbose_canvas_cursor ();
2278 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2280 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2282 display_model(msrc->model());
2286 MidiRegionView::set_frame_color()
2289 if (_selected && should_show_selection) {
2290 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2292 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2298 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2302 case FilterChannels:
2303 _force_channel = -1;
2306 _force_channel = mask;
2307 mask = 0xFFFF; // Show all notes as active (below)
2310 // Update notes for selection
2311 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2312 (*i)->on_channel_selection_change(mask);
2315 _last_channel_selection = mask;
2319 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2321 _model_name = model;
2322 _custom_device_mode = custom_device_mode;
2327 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2329 if (_selection.empty()) {
2333 PublicEditor& editor (trackview.editor());
2338 editor.get_cut_buffer().add (selection_as_cut_buffer());
2344 start_delta_command();
2346 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2351 delta_remove_note (*i);
2362 MidiRegionView::selection_as_cut_buffer () const
2366 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2367 notes.push_back (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2370 /* sort them into time order */
2372 Evoral::Sequence<Evoral::MusicalTime>::LaterNoteComparator cmp;
2373 sort (notes.begin(), notes.end(), cmp);
2375 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2382 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2388 start_delta_command (_("paste"));
2390 Evoral::MusicalTime beat_delta;
2391 Evoral::MusicalTime paste_pos_beats;
2392 Evoral::MusicalTime duration;
2393 Evoral::MusicalTime end_point;
2395 duration = mcb.notes().back()->end_time() - mcb.notes().front()->time();
2396 paste_pos_beats = frames_to_beats (pos - _region->position());
2397 beat_delta = mcb.notes().front()->time() - paste_pos_beats;
2398 paste_pos_beats = 0;
2400 _selection.clear ();
2402 for (int n = 0; n < (int) times; ++n) {
2404 for (NoteList::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2406 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2407 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2409 /* make all newly added notes selected */
2411 delta_add_note (copied_note, true);
2412 end_point = copied_note->end_time();
2415 paste_pos_beats += duration;
2418 /* if we pasted past the current end of the region, extend the region */
2420 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2421 nframes64_t region_end = _region->position() + _region->length() - 1;
2423 if (end_frame > region_end) {
2425 trackview.session().begin_reversible_command (_("paste"));
2427 XMLNode& before (_region->get_state());
2428 _region->set_length (end_frame, this);
2429 trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2435 struct EventNoteTimeEarlyFirstComparator {
2436 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2437 return a->note()->time() < b->note()->time();
2442 MidiRegionView::time_sort_events ()
2444 if (!_sort_needed) {
2448 EventNoteTimeEarlyFirstComparator cmp;
2451 _sort_needed = false;
2455 MidiRegionView::goto_next_note ()
2457 // nframes64_t pos = -1;
2458 bool use_next = false;
2460 if (_events.back()->selected()) {
2464 time_sort_events ();
2466 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2467 if ((*i)->selected()) {
2470 } else if (use_next) {
2472 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2477 /* use the first one */
2479 unique_select (_events.front());
2484 MidiRegionView::goto_previous_note ()
2486 // nframes64_t pos = -1;
2487 bool use_next = false;
2489 if (_events.front()->selected()) {
2493 time_sort_events ();
2495 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2496 if ((*i)->selected()) {
2499 } else if (use_next) {
2501 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2506 /* use the last one */
2508 unique_select (*(_events.rbegin()));
2512 MidiRegionView::selection_as_notelist (NoteList& selected)
2514 time_sort_events ();
2516 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2517 if ((*i)->selected()) {
2518 selected.push_back ((*i)->note());