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());
1194 const double y1 = midi_stream_view()->note_to_y(note->note());
1195 const double note_endpixel =
1196 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1198 ev->property_x1() = x;
1199 ev->property_y1() = y1;
1200 if (note->length() > 0) {
1201 ev->property_x2() = note_endpixel;
1203 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1205 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1207 if (note->length() == 0) {
1208 if (_active_notes) {
1209 assert(note->note() < 128);
1210 // If this note is already active there's a stuck note,
1211 // finish the old note rectangle
1212 if (_active_notes[note->note()]) {
1213 CanvasNote* const old_rect = _active_notes[note->note()];
1214 boost::shared_ptr<NoteType> old_note = old_rect->note();
1215 old_rect->property_x2() = x;
1216 old_rect->property_outline_what() = (guint32) 0xF;
1218 _active_notes[note->note()] = ev;
1220 /* outline all but right edge */
1221 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1223 /* outline all edges */
1224 ev->property_outline_what() = (guint32) 0xF;
1229 MidiRegionView::update_hit (CanvasHit* ev)
1231 boost::shared_ptr<NoteType> note = ev->note();
1233 const nframes64_t note_start_frames = beats_to_frames(note->time());
1234 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1235 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1236 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1241 /** Add a MIDI note to the view (with length).
1243 * If in sustained mode, notes with length 0 will be considered active
1244 * notes, and resolve_note should be called when the corresponding note off
1245 * event arrives, to properly display the note.
1248 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1250 CanvasNoteEvent* event = 0;
1252 assert(note->time() >= 0);
1253 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1255 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1257 if (midi_view()->note_mode() == Sustained) {
1259 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1261 update_note (ev_rect);
1265 MidiGhostRegion* gr;
1267 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1268 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1269 gr->add_note(ev_rect);
1273 } else if (midi_view()->note_mode() == Percussive) {
1275 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1277 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1279 update_hit (ev_diamond);
1288 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1289 note_selected(event, true);
1292 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1293 event->show_velocity();
1295 event->on_channel_selection_change(_last_channel_selection);
1296 _events.push_back(event);
1307 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1308 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1310 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1312 start_delta_command (_("step add"));
1313 delta_add_note (new_note, true, false);
1316 /* potentially extend region to hold new note */
1318 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1319 nframes64_t region_end = _region->position() + _region->length() - 1;
1321 if (end_frame > region_end) {
1322 _region->set_length (end_frame, this);
1329 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1331 assert(program.time >= 0);
1333 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1334 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1336 double height = midi_stream_view()->contents_height();
1338 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1339 new CanvasProgramChange(*this, *group,
1344 _custom_device_mode,
1345 program.time, program.channel, program.value));
1347 // Show unless program change is beyond the region bounds
1348 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1354 _pgm_changes.push_back(pgm_change);
1358 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1360 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1361 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1362 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1364 if (msb_control != 0) {
1365 msb = int(msb_control->get_float(true, time));
1366 cerr << "got msb " << msb;
1369 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1370 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1372 if (lsb_control != 0) {
1373 lsb = lsb_control->get_float(true, time);
1374 cerr << " got lsb " << lsb;
1377 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1378 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1379 float program_number = -1.0;
1380 if (program_control != 0) {
1381 program_number = program_control->get_float(true, time);
1382 cerr << " got program " << program_number << endl;
1385 key.msb = (int) floor(msb + 0.5);
1386 key.lsb = (int) floor(lsb + 0.5);
1387 key.program_number = (int) floor(program_number + 0.5);
1388 assert(key.is_sane());
1393 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1395 // TODO: Get the real event here and alter them at the original times
1396 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1397 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1398 if (msb_control != 0) {
1399 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1402 // TODO: Get the real event here and alter them at the original times
1403 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1404 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1405 if (lsb_control != 0) {
1406 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1409 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1410 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1412 assert(program_control != 0);
1413 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1419 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1421 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1422 alter_program_change(program_change_event, new_patch);
1426 MidiRegionView::previous_program(CanvasProgramChange& program)
1428 MIDI::Name::PatchPrimaryKey key;
1429 get_patch_key_at(program.event_time(), program.channel(), key);
1431 boost::shared_ptr<MIDI::Name::Patch> patch =
1432 MIDI::Name::MidiPatchManager::instance().previous_patch(
1434 _custom_device_mode,
1438 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1440 alter_program_change(program_change_event, patch->patch_primary_key());
1445 MidiRegionView::next_program(CanvasProgramChange& program)
1447 MIDI::Name::PatchPrimaryKey key;
1448 get_patch_key_at(program.event_time(), program.channel(), key);
1450 boost::shared_ptr<MIDI::Name::Patch> patch =
1451 MIDI::Name::MidiPatchManager::instance().next_patch(
1453 _custom_device_mode,
1457 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1459 alter_program_change(program_change_event, patch->patch_primary_key());
1464 MidiRegionView::delete_selection()
1466 if (_selection.empty()) {
1470 start_delta_command (_("delete selection"));
1472 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1473 if ((*i)->selected()) {
1474 _delta_command->remove((*i)->note());
1484 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1486 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1487 if ((*i)->selected() && (*i) != ev) {
1488 (*i)->selected(false);
1489 (*i)->hide_velocity();
1497 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1499 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1502 Selection::iterator tmp = i;
1505 (*i)->selected (false);
1506 _selection.erase (i);
1515 /* don't bother with removing this regionview from the editor selection,
1516 since we're about to add another note, and thus put/keep this
1517 regionview in the editor selection.
1520 if (!ev->selected()) {
1521 add_to_selection (ev);
1526 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1529 clear_selection_except(ev);
1534 if (!ev->selected()) {
1535 add_to_selection (ev);
1539 /* find end of latest note selected, select all between that and the start of "ev" */
1541 Evoral::MusicalTime earliest = DBL_MAX;
1542 Evoral::MusicalTime latest = 0;
1544 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1545 if ((*i)->note()->end_time() > latest) {
1546 latest = (*i)->note()->end_time();
1548 if ((*i)->note()->time() < earliest) {
1549 earliest = (*i)->note()->time();
1553 if (ev->note()->end_time() > latest) {
1554 latest = ev->note()->end_time();
1557 if (ev->note()->time() < earliest) {
1558 earliest = ev->note()->time();
1561 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1563 /* find notes entirely within OR spanning the earliest..latest range */
1565 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1566 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1567 add_to_selection (*i);
1571 /* if events were guaranteed to be time sorted, we could do this.
1572 but as of sept 10th 2009, they no longer are.
1575 if ((*i)->note()->time() > latest) {
1584 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1586 remove_from_selection (ev);
1590 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1600 // TODO: Make this faster by storing the last updated selection rect, and only
1601 // adjusting things that are in the area that appears/disappeared.
1602 // We probably need a tree to be able to find events in O(log(n)) time.
1604 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1606 /* check if any corner of the note is inside the rect
1609 1) this is computing "touched by", not "contained by" the rect.
1610 2) this does not require that events be sorted in time.
1613 const double ix1 = (*i)->x1();
1614 const double ix2 = (*i)->x2();
1615 const double iy1 = (*i)->y1();
1616 const double iy2 = (*i)->y2();
1618 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1619 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1620 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1621 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1624 if (!(*i)->selected()) {
1625 add_to_selection (*i);
1627 } else if ((*i)->selected()) {
1628 // Not inside rectangle
1629 remove_from_selection (*i);
1635 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1637 Selection::iterator i = _selection.find (ev);
1639 if (i != _selection.end()) {
1640 _selection.erase (i);
1643 ev->selected (false);
1644 ev->hide_velocity ();
1646 if (_selection.empty()) {
1647 PublicEditor& editor (trackview.editor());
1648 editor.get_selection().remove (this);
1653 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1655 bool add_mrv_selection = false;
1657 if (_selection.empty()) {
1658 add_mrv_selection = true;
1661 if (_selection.insert (ev).second) {
1662 ev->selected (true);
1663 play_midi_note ((ev)->note());
1666 if (add_mrv_selection) {
1667 PublicEditor& editor (trackview.editor());
1668 editor.get_selection().add (this);
1673 MidiRegionView::move_selection(double dx, double dy)
1675 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1676 (*i)->move_event(dx, dy);
1681 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1683 assert (!_selection.empty());
1685 uint8_t lowest_note_in_selection = 127;
1686 uint8_t highest_note_in_selection = 0;
1687 uint8_t highest_note_difference = 0;
1689 // find highest and lowest notes first
1691 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1692 uint8_t pitch = (*i)->note()->note();
1693 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1694 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1698 cerr << "dnote: " << (int) dnote << endl;
1699 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1700 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1701 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1702 << int(highest_note_in_selection) << endl;
1703 cerr << "selection size: " << _selection.size() << endl;
1704 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1707 // Make sure the note pitch does not exceed the MIDI standard range
1708 if (highest_note_in_selection + dnote > 127) {
1709 highest_note_difference = highest_note_in_selection - 127;
1712 start_diff_command(_("move notes"));
1714 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1716 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1719 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1721 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1724 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1730 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1732 uint8_t original_pitch = (*i)->note()->note();
1733 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1735 // keep notes in standard midi range
1736 clamp_to_0_127(new_pitch);
1738 // keep original pitch if note is dragged outside valid midi range
1739 if ((original_pitch != 0 && new_pitch == 0)
1740 || (original_pitch != 127 && new_pitch == 127)) {
1741 new_pitch = original_pitch;
1744 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1745 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1747 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1752 // care about notes being moved beyond the upper/lower bounds on the canvas
1753 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1754 highest_note_in_selection > midi_stream_view()->highest_note()) {
1755 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1760 MidiRegionView::snap_pixel_to_frame(double x)
1762 PublicEditor& editor = trackview.editor();
1763 // x is region relative, convert it to global absolute frames
1764 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1765 editor.snap_to(frame);
1766 return frame - _region->position(); // convert back to region relative
1770 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1772 PublicEditor& editor = trackview.editor();
1773 // x is region relative, convert it to global absolute frames
1774 nframes64_t frame = x + _region->position();
1775 editor.snap_to(frame);
1776 return frame - _region->position(); // convert back to region relative
1780 MidiRegionView::snap_to_pixel(double x)
1782 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1786 MidiRegionView::get_position_pixels()
1788 nframes64_t region_frame = get_position();
1789 return trackview.editor().frame_to_pixel(region_frame);
1793 MidiRegionView::get_end_position_pixels()
1795 nframes64_t frame = get_position() + get_duration ();
1796 return trackview.editor().frame_to_pixel(frame);
1800 MidiRegionView::beats_to_frames(double beats) const
1802 return _time_converter.to(beats);
1806 MidiRegionView::frames_to_beats(nframes64_t frames) const
1808 return _time_converter.from(frames);
1812 MidiRegionView::begin_resizing (bool /*at_front*/)
1814 _resize_data.clear();
1816 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1817 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1819 // only insert CanvasNotes into the map
1821 NoteResizeData *resize_data = new NoteResizeData();
1822 resize_data->canvas_note = note;
1824 // create a new SimpleRect from the note which will be the resize preview
1825 SimpleRect *resize_rect = new SimpleRect(
1826 *group, note->x1(), note->y1(), note->x2(), note->y2());
1828 // calculate the colors: get the color settings
1829 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1830 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1833 // make the resize preview notes more transparent and bright
1834 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1836 // calculate color based on note velocity
1837 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1838 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1842 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1843 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1845 resize_data->resize_rect = resize_rect;
1846 _resize_data.push_back(resize_data);
1852 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1854 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1855 SimpleRect* resize_rect = (*i)->resize_rect;
1856 CanvasNote* canvas_note = (*i)->canvas_note;
1861 current_x = canvas_note->x1() + delta_x;
1863 // x is in track relative, transform it to region relative
1864 current_x = delta_x - get_position_pixels();
1868 current_x = canvas_note->x2() + delta_x;
1870 // x is in track relative, transform it to region relative
1871 current_x = delta_x - get_end_position_pixels ();
1876 resize_rect->property_x1() = snap_to_pixel(current_x);
1877 resize_rect->property_x2() = canvas_note->x2();
1879 resize_rect->property_x2() = snap_to_pixel(current_x);
1880 resize_rect->property_x1() = canvas_note->x1();
1886 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1888 start_diff_command(_("resize notes"));
1890 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1891 CanvasNote* canvas_note = (*i)->canvas_note;
1892 SimpleRect* resize_rect = (*i)->resize_rect;
1893 const double region_start = get_position_pixels();
1898 current_x = canvas_note->x1() + delta_x;
1900 // x is in track relative, transform it to region relative
1901 current_x = region_start + delta_x;
1905 current_x = canvas_note->x2() + delta_x;
1907 // x is in track relative, transform it to region relative
1908 current_x = region_start + delta_x;
1912 current_x = snap_pixel_to_frame (current_x);
1913 current_x = frames_to_beats (current_x);
1915 if (at_front && current_x < canvas_note->note()->end_time()) {
1916 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
1918 double len = canvas_note->note()->time() - current_x;
1919 len += canvas_note->note()->length();
1922 /* XXX convert to beats */
1923 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1928 double len = current_x - canvas_note->note()->time();
1931 /* XXX convert to beats */
1932 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
1940 _resize_data.clear();
1945 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
1947 uint8_t new_velocity;
1950 new_velocity = event->note()->velocity() + velocity;
1951 clamp_to_0_127(new_velocity);
1953 new_velocity = velocity;
1956 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
1960 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
1965 new_note = event->note()->note() + note;
1970 clamp_to_0_127 (new_note);
1971 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
1975 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
1977 bool change_start = false;
1978 bool change_length = false;
1979 Evoral::MusicalTime new_start;
1980 Evoral::MusicalTime new_length;
1982 /* NOTE: the semantics of the two delta arguments are slightly subtle:
1984 front_delta: if positive - move the start of the note later in time (shortening it)
1985 if negative - move the start of the note earlier in time (lengthening it)
1987 end_delta: if positive - move the end of the note later in time (lengthening it)
1988 if negative - move the end of the note earlier in time (shortening it)
1992 if (front_delta < 0) {
1994 if (event->note()->time() < -front_delta) {
1997 new_start = event->note()->time() + front_delta; // moves earlier
2000 /* start moved toward zero, so move the end point out to where it used to be.
2001 Note that front_delta is negative, so this increases the length.
2004 new_length = event->note()->length() - front_delta;
2005 change_start = true;
2006 change_length = true;
2010 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2012 if (new_pos < event->note()->end_time()) {
2013 new_start = event->note()->time() + front_delta;
2014 /* start moved toward the end, so move the end point back to where it used to be */
2015 new_length = event->note()->length() - front_delta;
2016 change_start = true;
2017 change_length = true;
2024 bool can_change = true;
2025 if (end_delta < 0) {
2026 if (event->note()->length() < -end_delta) {
2032 new_length = event->note()->length() + end_delta;
2033 change_length = true;
2038 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2041 if (change_length) {
2042 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2047 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2049 Evoral::MusicalTime new_time;
2053 if (event->note()->time() < -delta) {
2056 new_time = event->note()->time() + delta;
2059 new_time = event->note()->time() + delta;
2065 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2069 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2073 if (_selection.empty()) {
2088 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2089 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2095 start_diff_command(_("change velocities"));
2097 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2098 Selection::iterator next = i;
2100 change_note_velocity (*i, delta, true);
2109 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2111 if (_selection.empty()) {
2128 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2130 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2134 if ((int8_t) (*i)->note()->note() + delta > 127) {
2141 start_diff_command (_("transpose"));
2143 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2144 Selection::iterator next = i;
2146 change_note_note (*i, delta, true);
2154 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2156 Evoral::MusicalTime delta;
2161 /* grab the current grid distance */
2163 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2165 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2166 cerr << "Grid type not available as beats - TO BE FIXED\n";
2175 start_diff_command (_("change note lengths"));
2177 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2178 Selection::iterator next = i;
2181 /* note the negation of the delta for start */
2183 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2192 MidiRegionView::nudge_notes (bool forward)
2194 if (_selection.empty()) {
2198 /* pick a note as the point along the timeline to get the nudge distance.
2199 its not necessarily the earliest note, so we may want to pull the notes out
2200 into a vector and sort before using the first one.
2203 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2205 nframes64_t distance;
2207 if ((distance = trackview.editor().get_nudge_distance (ref_point, unused)) == 0) {
2209 /* no nudge distance set - use grid */
2211 nframes64_t next_pos = ref_point;
2214 /* XXX need check on max_frames, but that needs max_frames64 or something */
2217 if (next_pos == 0) {
2223 cerr << "ref point was " << ref_point << " next was " << next_pos;
2224 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2225 distance = ref_point - next_pos;
2226 cerr << " final is " << next_pos << " distance = " << distance << endl;
2229 if (distance == 0) {
2233 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2239 start_diff_command (_("nudge"));
2241 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2242 Selection::iterator next = i;
2244 change_note_time (*i, delta, true);
2252 MidiRegionView::change_channel(uint8_t channel)
2254 start_diff_command(_("change channel"));
2255 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2256 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2263 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2265 if (_mouse_state == SelectTouchDragging) {
2266 note_selected(ev, true);
2269 PublicEditor& editor (trackview.editor());
2271 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2272 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2273 editor.show_verbose_canvas_cursor_with (buf);
2277 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2279 PublicEditor& editor (trackview.editor());
2280 editor.hide_verbose_canvas_cursor ();
2285 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2287 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2289 display_model(msrc->model());
2293 MidiRegionView::set_frame_color()
2296 if (_selected && should_show_selection) {
2297 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2299 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2305 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2309 case FilterChannels:
2310 _force_channel = -1;
2313 _force_channel = mask;
2314 mask = 0xFFFF; // Show all notes as active (below)
2317 // Update notes for selection
2318 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2319 (*i)->on_channel_selection_change(mask);
2322 _last_channel_selection = mask;
2326 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2328 _model_name = model;
2329 _custom_device_mode = custom_device_mode;
2334 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2336 if (_selection.empty()) {
2340 PublicEditor& editor (trackview.editor());
2345 editor.get_cut_buffer().add (selection_as_cut_buffer());
2351 start_delta_command();
2353 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2358 delta_remove_note (*i);
2369 MidiRegionView::selection_as_cut_buffer () const
2373 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2374 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
2377 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2384 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2390 start_delta_command (_("paste"));
2392 Evoral::MusicalTime beat_delta;
2393 Evoral::MusicalTime paste_pos_beats;
2394 Evoral::MusicalTime duration;
2395 Evoral::MusicalTime end_point;
2397 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2398 paste_pos_beats = frames_to_beats (pos - _region->position());
2399 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2400 paste_pos_beats = 0;
2402 _selection.clear ();
2404 for (int n = 0; n < (int) times; ++n) {
2406 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2408 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2409 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2411 /* make all newly added notes selected */
2413 delta_add_note (copied_note, true);
2414 end_point = copied_note->end_time();
2417 paste_pos_beats += duration;
2420 /* if we pasted past the current end of the region, extend the region */
2422 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2423 nframes64_t region_end = _region->position() + _region->length() - 1;
2425 if (end_frame > region_end) {
2427 trackview.session().begin_reversible_command (_("paste"));
2429 XMLNode& before (_region->get_state());
2430 _region->set_length (end_frame, this);
2431 trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
2437 struct EventNoteTimeEarlyFirstComparator {
2438 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2439 return a->note()->time() < b->note()->time();
2444 MidiRegionView::time_sort_events ()
2446 if (!_sort_needed) {
2450 EventNoteTimeEarlyFirstComparator cmp;
2453 _sort_needed = false;
2457 MidiRegionView::goto_next_note ()
2459 // nframes64_t pos = -1;
2460 bool use_next = false;
2462 if (_events.back()->selected()) {
2466 time_sort_events ();
2468 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2469 if ((*i)->selected()) {
2472 } else if (use_next) {
2474 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2479 /* use the first one */
2481 unique_select (_events.front());
2486 MidiRegionView::goto_previous_note ()
2488 // nframes64_t pos = -1;
2489 bool use_next = false;
2491 if (_events.front()->selected()) {
2495 time_sort_events ();
2497 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2498 if ((*i)->selected()) {
2501 } else if (use_next) {
2503 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2508 /* use the last one */
2510 unique_select (*(_events.rbegin()));
2514 MidiRegionView::selection_as_notelist (Notes& selected)
2516 time_sort_events ();
2518 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2519 if ((*i)->selected()) {
2520 selected.insert ((*i)->note());