add 8 more beat subdivisions, as per #3126
[ardour2.git] / gtk2_ardour / midi_region_view.cc
bloba0ccd7b9ad00f376c8552e1b539c6b43a7803550
1 /*
2 Copyright (C) 2001-2007 Paul Davis
3 Author: Dave Robillard
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.
20 #include <cmath>
21 #include <cassert>
22 #include <algorithm>
23 #include <ostream>
25 #include <gtkmm.h>
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/Control.hpp"
45 #include "automation_region_view.h"
46 #include "automation_time_axis.h"
47 #include "canvas-hit.h"
48 #include "canvas-note.h"
49 #include "canvas-program-change.h"
50 #include "ghostregion.h"
51 #include "gui_thread.h"
52 #include "keyboard.h"
53 #include "midi_cut_buffer.h"
54 #include "midi_list_editor.h"
55 #include "midi_region_view.h"
56 #include "midi_streamview.h"
57 #include "midi_time_axis.h"
58 #include "midi_time_axis.h"
59 #include "midi_util.h"
60 #include "public_editor.h"
61 #include "selection.h"
62 #include "simpleline.h"
63 #include "streamview.h"
64 #include "utils.h"
66 #include "i18n.h"
68 using namespace ARDOUR;
69 using namespace PBD;
70 using namespace Editing;
71 using namespace ArdourCanvas;
72 using Gtkmm2ext::Keyboard;
74 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
75 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
76 : RegionView (parent, tv, r, spu, basic_color)
77 , _force_channel(-1)
78 , _last_channel_selection(0xFFFF)
79 , _default_note_length(1.0)
80 , _current_range_min(0)
81 , _current_range_max(0)
82 , _model_name(string())
83 , _custom_device_mode(string())
84 , _active_notes(0)
85 , _note_group(new ArdourCanvas::Group(*parent))
86 , _delta_command(0)
87 , _diff_command(0)
88 , _mouse_state(None)
89 , _pressed_button(0)
90 , _sort_needed (true)
91 , _optimization_iterator (_events.end())
92 , _list_editor (0)
93 , no_sound_notes (false)
95 _note_group->raise_to_top();
98 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
99 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
100 TimeAxisViewItem::Visibility visibility)
101 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
102 , _force_channel(-1)
103 , _last_channel_selection(0xFFFF)
104 , _default_note_length(1.0)
105 , _model_name(string())
106 , _custom_device_mode(string())
107 , _active_notes(0)
108 , _note_group(new ArdourCanvas::Group(*parent))
109 , _delta_command(0)
110 , _diff_command(0)
111 , _mouse_state(None)
112 , _pressed_button(0)
113 , _sort_needed (true)
114 , _optimization_iterator (_events.end())
115 , _list_editor (0)
116 , no_sound_notes (false)
119 _note_group->raise_to_top();
123 MidiRegionView::MidiRegionView (const MidiRegionView& other)
124 : sigc::trackable(other)
125 , RegionView (other)
126 , _force_channel(-1)
127 , _last_channel_selection(0xFFFF)
128 , _default_note_length(1.0)
129 , _model_name(string())
130 , _custom_device_mode(string())
131 , _active_notes(0)
132 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
133 , _delta_command(0)
134 , _diff_command(0)
135 , _mouse_state(None)
136 , _pressed_button(0)
137 , _sort_needed (true)
138 , _optimization_iterator (_events.end())
139 , _list_editor (0)
140 , no_sound_notes (false)
142 Gdk::Color c;
143 int r,g,b,a;
145 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
146 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
148 init (c, false);
151 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
152 : RegionView (other, boost::shared_ptr<Region> (region))
153 , _force_channel(-1)
154 , _last_channel_selection(0xFFFF)
155 , _default_note_length(1.0)
156 , _model_name(string())
157 , _custom_device_mode(string())
158 , _active_notes(0)
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
160 , _delta_command(0)
161 , _diff_command(0)
162 , _mouse_state(None)
163 , _pressed_button(0)
164 , _sort_needed (true)
165 , _optimization_iterator (_events.end())
166 , _list_editor (0)
167 , no_sound_notes (false)
169 Gdk::Color c;
170 int r,g,b,a;
172 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
173 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
175 init (c, true);
178 void
179 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
181 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
182 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
183 gui_context());
185 if (wfd) {
186 midi_region()->midi_source(0)->load_model();
189 _model = midi_region()->midi_source(0)->model();
190 _enable_display = false;
192 RegionView::init (basic_color, false);
194 compute_colors (basic_color);
196 set_height (trackview.current_height());
198 region_muted ();
199 region_sync_changed ();
200 region_resized (ARDOUR::bounds_change);
201 region_locked ();
203 reset_width_dependent_items (_pixel_width);
205 set_colors ();
207 _enable_display = true;
208 if (_model) {
209 if (wfd) {
210 display_model (_model);
214 group->raise_to_top();
215 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
217 midi_view()->signal_channel_mode_changed().connect(
218 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
220 midi_view()->signal_midi_patch_settings_changed().connect(
221 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
224 bool
225 MidiRegionView::canvas_event(GdkEvent* ev)
227 PublicEditor& editor (trackview.editor());
229 if (!editor.internal_editing()) {
230 return false;
233 static double drag_start_x, drag_start_y;
234 static double last_x, last_y;
235 double event_x, event_y;
236 nframes64_t event_frame = 0;
237 bool fine;
239 static ArdourCanvas::SimpleRect* drag_rect = 0;
241 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
242 to its items, which means that ev->type == GDK_SCROLL will never be seen
245 switch (ev->type) {
246 case GDK_SCROLL:
247 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
249 if (ev->scroll.direction == GDK_SCROLL_UP) {
250 change_velocities (true, fine, false);
251 return true;
252 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
253 change_velocities (false, fine, false);
254 return true;
255 } else {
256 return false;
258 break;
260 case GDK_KEY_PRESS:
262 /* since GTK bindings are generally activated on press, and since
263 detectable auto-repeat is the name of the game and only sends
264 repeated presses, carry out key actions at key press, not release.
267 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
268 _mouse_state = SelectTouchDragging;
269 return true;
271 } else if (ev->key.keyval == GDK_Escape) {
272 clear_selection();
273 _mouse_state = None;
275 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
277 bool start = (ev->key.keyval == GDK_comma);
278 bool end = (ev->key.keyval == GDK_period);
279 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
280 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
282 change_note_lengths (fine, shorter, start, end);
284 return true;
286 } else if (ev->key.keyval == GDK_Delete) {
288 delete_selection();
289 return true;
291 } else if (ev->key.keyval == GDK_Tab) {
293 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
294 goto_previous_note ();
295 } else {
296 goto_next_note ();
298 return true;
300 } else if (ev->key.keyval == GDK_Up) {
302 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
303 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
305 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
306 change_velocities (true, fine, allow_smush);
307 } else {
308 transpose (true, fine, allow_smush);
310 return true;
312 } else if (ev->key.keyval == GDK_Down) {
314 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
315 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
317 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
318 change_velocities (false, fine, allow_smush);
319 } else {
320 transpose (false, fine, allow_smush);
322 return true;
324 } else if (ev->key.keyval == GDK_Left) {
326 nudge_notes (false);
327 return true;
329 } else if (ev->key.keyval == GDK_Right) {
331 nudge_notes (true);
332 return true;
334 } else if (ev->key.keyval == GDK_Control_L) {
335 return true;
337 } else if (ev->key.keyval == GDK_r) {
338 /* if we're not step editing, this really doesn't matter */
339 midi_view()->step_edit_rest ();
340 return true;
343 return false;
345 case GDK_KEY_RELEASE:
346 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
347 _mouse_state = None;
348 return true;
350 return false;
352 case GDK_BUTTON_PRESS:
353 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
354 _pressed_button = ev->button.button;
355 _mouse_state = Pressed;
356 return true;
358 _pressed_button = ev->button.button;
359 return true;
361 case GDK_2BUTTON_PRESS:
362 return true;
364 case GDK_ENTER_NOTIFY:
365 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
366 Keyboard::magic_widget_grab_focus();
367 group->grab_focus();
368 break;
370 case GDK_MOTION_NOTIFY:
371 event_x = ev->motion.x;
372 event_y = ev->motion.y;
373 group->w2i(event_x, event_y);
375 // convert event_x to global frame
376 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
377 trackview.editor().snap_to(event_frame);
378 // convert event_frame back to local coordinates relative to position
379 event_frame -= _region->position();
381 switch (_mouse_state) {
382 case Pressed: // Drag start
384 // Select drag start
385 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
386 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
387 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
388 last_x = event_x;
389 last_y = event_y;
390 drag_start_x = event_x;
391 drag_start_y = event_y;
393 drag_rect = new ArdourCanvas::SimpleRect(*group);
394 drag_rect->property_x1() = event_x;
395 drag_rect->property_y1() = event_y;
396 drag_rect->property_x2() = event_x;
397 drag_rect->property_y2() = event_y;
398 drag_rect->property_outline_what() = 0xFF;
399 drag_rect->property_outline_color_rgba()
400 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
401 drag_rect->property_fill_color_rgba()
402 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
404 _mouse_state = SelectRectDragging;
405 return true;
407 // Add note drag start
408 } else if (editor.current_mouse_mode() == MouseRange) {
409 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
410 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
411 last_x = event_x;
412 last_y = event_y;
413 drag_start_x = event_x;
414 drag_start_y = event_y;
416 drag_rect = new ArdourCanvas::SimpleRect(*group);
417 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
419 drag_rect->property_y1() = midi_stream_view()->note_to_y(
420 midi_stream_view()->y_to_note(event_y));
421 drag_rect->property_x2() = event_x;
422 drag_rect->property_y2() = drag_rect->property_y1()
423 + floor(midi_stream_view()->note_height());
424 drag_rect->property_outline_what() = 0xFF;
425 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
426 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
428 _mouse_state = AddDragging;
429 return true;
432 return false;
434 case SelectRectDragging: // Select drag motion
435 case AddDragging: // Add note drag motion
436 if (ev->motion.is_hint) {
437 int t_x;
438 int t_y;
439 GdkModifierType state;
440 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
441 event_x = t_x;
442 event_y = t_y;
445 if (_mouse_state == AddDragging)
446 event_x = trackview.editor().frame_to_pixel(event_frame);
448 if (drag_rect) {
449 if (event_x > drag_start_x)
450 drag_rect->property_x2() = event_x;
451 else
452 drag_rect->property_x1() = event_x;
455 if (drag_rect && _mouse_state == SelectRectDragging) {
456 if (event_y > drag_start_y)
457 drag_rect->property_y2() = event_y;
458 else
459 drag_rect->property_y1() = event_y;
461 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
464 last_x = event_x;
465 last_y = event_y;
467 case SelectTouchDragging:
468 return false;
470 default:
471 break;
473 break;
475 case GDK_BUTTON_RELEASE:
476 event_x = ev->motion.x;
477 event_y = ev->motion.y;
478 group->w2i(event_x, event_y);
479 group->ungrab(ev->button.time);
480 event_frame = trackview.editor().pixel_to_frame(event_x);
482 if (ev->button.button == 3) {
483 return false;
484 } else if (_pressed_button != 1) {
485 return false;
488 switch (_mouse_state) {
489 case Pressed: // Clicked
490 switch (editor.current_mouse_mode()) {
491 case MouseObject:
492 case MouseTimeFX:
493 clear_selection();
494 break;
495 case MouseRange:
496 create_note_at(event_x, event_y, _default_note_length);
497 break;
498 default:
499 break;
501 _mouse_state = None;
502 break;
503 case SelectRectDragging: // Select drag done
504 _mouse_state = None;
505 delete drag_rect;
506 drag_rect = 0;
507 break;
508 case AddDragging: // Add drag done
509 _mouse_state = None;
510 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
511 const double x = drag_rect->property_x1();
512 const double length = trackview.editor().pixel_to_frame(
513 drag_rect->property_x2() - drag_rect->property_x1());
515 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
518 delete drag_rect;
519 drag_rect = 0;
520 default: break;
523 default: break;
526 return false;
529 void
530 MidiRegionView::show_list_editor ()
532 if (!_list_editor) {
533 _list_editor = new MidiListEditor (trackview.session(), midi_region());
535 _list_editor->present ();
538 /** Add a note to the model, and the view, at a canvas (click) coordinate.
539 * \param x horizontal position in pixels
540 * \param y vertical position in pixels
541 * \param length duration of the note in beats */
542 void
543 MidiRegionView::create_note_at(double x, double y, double length)
545 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
546 MidiStreamView* const view = mtv->midi_view();
548 double note = midi_stream_view()->y_to_note(y);
550 assert(note >= 0.0);
551 assert(note <= 127.0);
553 // Start of note in frames relative to region start
554 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
555 assert(start_frames >= 0);
557 // Snap length
558 length = frames_to_beats(
559 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
561 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
562 frames_to_beats(start_frames + _region->start()), length,
563 (uint8_t)note, 0x40));
565 view->update_note_range(new_note->note());
567 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
568 cmd->add(new_note);
569 _model->apply_command(*trackview.session(), cmd);
571 play_midi_note (new_note);
574 void
575 MidiRegionView::clear_events()
577 clear_selection();
579 MidiGhostRegion* gr;
580 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
581 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
582 gr->clear_events();
586 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
587 delete *i;
590 _events.clear();
591 _pgm_changes.clear();
592 _sys_exes.clear();
593 _optimization_iterator = _events.end();
597 void
598 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
600 _model = model;
601 content_connection.disconnect ();
602 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
604 clear_events ();
606 if (_enable_display) {
607 redisplay_model();
612 void
613 MidiRegionView::start_delta_command(string name)
615 if (!_delta_command) {
616 _delta_command = _model->new_delta_command(name);
620 void
621 MidiRegionView::start_diff_command(string name)
623 if (!_diff_command) {
624 _diff_command = _model->new_diff_command(name);
628 void
629 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
631 if (_delta_command) {
632 _delta_command->add(note);
634 if (selected) {
635 _marked_for_selection.insert(note);
637 if (show_velocity) {
638 _marked_for_velocity.insert(note);
642 void
643 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
645 if (_delta_command && ev->note()) {
646 _delta_command->remove(ev->note());
650 void
651 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
652 MidiModel::DiffCommand::Property property,
653 uint8_t val)
655 if (_diff_command) {
656 _diff_command->change (ev->note(), property, val);
660 void
661 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
662 MidiModel::DiffCommand::Property property,
663 Evoral::MusicalTime val)
665 if (_diff_command) {
666 _diff_command->change (ev->note(), property, val);
670 void
671 MidiRegionView::apply_delta()
673 if (!_delta_command) {
674 return;
677 // Mark all selected notes for selection when model reloads
678 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
679 _marked_for_selection.insert((*i)->note());
682 _model->apply_command(*trackview.session(), _delta_command);
683 _delta_command = 0;
684 midi_view()->midi_track()->playlist_modified();
686 _marked_for_selection.clear();
687 _marked_for_velocity.clear();
690 void
691 MidiRegionView::apply_diff ()
693 if (!_diff_command) {
694 return;
697 _model->apply_command(*trackview.session(), _diff_command);
698 _diff_command = 0;
699 midi_view()->midi_track()->playlist_modified();
701 _marked_for_velocity.clear();
704 void
705 MidiRegionView::apply_delta_as_subcommand()
707 if (!_delta_command) {
708 return;
711 // Mark all selected notes for selection when model reloads
712 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
713 _marked_for_selection.insert((*i)->note());
716 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
717 _delta_command = 0;
718 midi_view()->midi_track()->playlist_modified();
720 _marked_for_selection.clear();
721 _marked_for_velocity.clear();
724 void
725 MidiRegionView::apply_diff_as_subcommand()
727 if (!_diff_command) {
728 return;
731 // Mark all selected notes for selection when model reloads
732 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
733 _marked_for_selection.insert((*i)->note());
736 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
737 _diff_command = 0;
738 midi_view()->midi_track()->playlist_modified();
740 _marked_for_selection.clear();
741 _marked_for_velocity.clear();
744 void
745 MidiRegionView::abort_command()
747 delete _delta_command;
748 _delta_command = 0;
749 delete _diff_command;
750 _diff_command = 0;
751 clear_selection();
754 CanvasNoteEvent*
755 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
757 if (_optimization_iterator != _events.end()) {
758 ++_optimization_iterator;
761 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
762 return *_optimization_iterator;
765 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
766 if ((*_optimization_iterator)->note() == note) {
767 return *_optimization_iterator;
771 return 0;
774 void
775 MidiRegionView::redisplay_model()
777 // Don't redisplay the model if we're currently recording and displaying that
778 if (_active_notes) {
779 return;
782 if (!_model) {
783 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
784 return;
787 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
788 (*i)->invalidate ();
791 MidiModel::ReadLock lock(_model->read_lock());
793 MidiModel::Notes& notes (_model->notes());
794 _optimization_iterator = _events.begin();
796 cerr << "++++++++++ MIDI REdisplay\n";
798 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
800 boost::shared_ptr<NoteType> note (*n);
801 CanvasNoteEvent* cne;
802 bool visible;
804 if (note_in_region_range (note, visible)) {
806 if ((cne = find_canvas_note (note)) != 0) {
808 cne->validate ();
810 CanvasNote* cn;
811 CanvasHit* ch;
813 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
814 update_note (cn);
815 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
816 update_hit (ch);
819 if (visible) {
820 cne->show ();
821 } else {
822 cne->hide ();
825 } else {
827 add_note (note, visible);
830 } else {
832 if ((cne = find_canvas_note (note)) != 0) {
833 cne->validate ();
834 cne->hide ();
840 /* remove note items that are no longer valid */
842 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
843 if (!(*i)->valid ()) {
844 delete *i;
845 i = _events.erase (i);
846 } else {
847 ++i;
851 display_sysexes();
852 display_program_changes();
854 _marked_for_selection.clear ();
855 _marked_for_velocity.clear ();
857 /* we may have caused _events to contain things out of order (e.g. if a note
858 moved earlier or later). we don't generally need them in time order, but
859 make a note that a sort is required for those cases that require it.
862 _sort_needed = true;
865 void
866 MidiRegionView::display_program_changes()
868 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
869 if (!control) {
870 return;
873 Glib::Mutex::Lock lock (control->list()->lock());
875 uint8_t channel = control->parameter().channel();
877 for (AutomationList::const_iterator event = control->list()->begin();
878 event != control->list()->end(); ++event) {
879 double event_time = (*event)->when;
880 double program_number = floor((*event)->value + 0.5);
882 // Get current value of bank select MSB at time of the program change
883 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
884 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
885 uint8_t msb = 0;
886 if (msb_control != 0) {
887 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
890 // Get current value of bank select LSB at time of the program change
891 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
892 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
893 uint8_t lsb = 0;
894 if (lsb_control != 0) {
895 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
898 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
900 boost::shared_ptr<MIDI::Name::Patch> patch =
901 MIDI::Name::MidiPatchManager::instance().find_patch(
902 _model_name, _custom_device_mode, channel, patch_key);
904 PCEvent program_change(event_time, uint8_t(program_number), channel);
906 if (patch != 0) {
907 add_pgm_change(program_change, patch->name());
908 } else {
909 char buf[4];
910 snprintf(buf, 4, "%d", int(program_number));
911 add_pgm_change(program_change, buf);
916 void
917 MidiRegionView::display_sysexes()
919 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
920 Evoral::MusicalTime time = (*i)->time();
921 assert(time >= 0);
923 ostringstream str;
924 str << hex;
925 for (uint32_t b = 0; b < (*i)->size(); ++b) {
926 str << int((*i)->buffer()[b]);
927 if (b != (*i)->size() -1) {
928 str << " ";
931 string text = str.str();
933 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
935 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
937 double height = midi_stream_view()->contents_height();
939 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
940 new CanvasSysEx(*this, *group, text, height, x, 1.0));
942 // Show unless program change is beyond the region bounds
943 if (time - _region->start() >= _region->length() || time < _region->start()) {
944 sysex->hide();
945 } else {
946 sysex->show();
949 _sys_exes.push_back(sysex);
954 MidiRegionView::~MidiRegionView ()
956 in_destructor = true;
958 note_delete_connection.disconnect ();
960 delete _list_editor;
962 RegionViewGoingAway (this); /* EMIT_SIGNAL */
964 if (_active_notes) {
965 end_write();
968 _selection.clear();
969 clear_events();
970 delete _note_group;
971 delete _delta_command;
974 void
975 MidiRegionView::region_resized (const PropertyChange& what_changed)
977 RegionView::region_resized(what_changed);
979 if (what_changed.contains (ARDOUR::Properties::position)) {
980 set_duration(_region->length(), 0);
981 if (_enable_display) {
982 redisplay_model();
987 void
988 MidiRegionView::reset_width_dependent_items (double pixel_width)
990 RegionView::reset_width_dependent_items(pixel_width);
991 assert(_pixel_width == pixel_width);
993 if (_enable_display) {
994 redisplay_model();
998 void
999 MidiRegionView::set_height (double height)
1001 static const double FUDGE = 2.0;
1002 const double old_height = _height;
1003 RegionView::set_height(height);
1004 _height = height - FUDGE;
1006 apply_note_range(midi_stream_view()->lowest_note(),
1007 midi_stream_view()->highest_note(),
1008 height != old_height + FUDGE);
1010 if (name_pixbuf) {
1011 name_pixbuf->raise_to_top();
1016 /** Apply the current note range from the stream view
1017 * by repositioning/hiding notes as necessary
1019 void
1020 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1022 if (!_enable_display) {
1023 return;
1026 if (!force && _current_range_min == min && _current_range_max == max) {
1027 return;
1030 _current_range_min = min;
1031 _current_range_max = max;
1033 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1034 CanvasNoteEvent* event = *i;
1035 boost::shared_ptr<NoteType> note (event->note());
1037 if (note->note() < _current_range_min ||
1038 note->note() > _current_range_max) {
1039 event->hide();
1040 } else {
1041 event->show();
1044 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1046 const double y1 = midi_stream_view()->note_to_y(note->note());
1047 const double y2 = y1 + floor(midi_stream_view()->note_height());
1049 cnote->property_y1() = y1;
1050 cnote->property_y2() = y2;
1052 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1054 double x = trackview.editor().frame_to_pixel(
1055 beats_to_frames(note->time()) - _region->start());
1056 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1057 double y = midi_stream_view()->note_to_y(event->note()->note())
1058 + ((diamond_size-2.0) / 4.0);
1060 chit->set_height (diamond_size);
1061 chit->move (x - chit->x1(), y - chit->y1());
1062 chit->show ();
1067 GhostRegion*
1068 MidiRegionView::add_ghost (TimeAxisView& tv)
1070 CanvasNote* note;
1072 double unit_position = _region->position () / samples_per_unit;
1073 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1074 MidiGhostRegion* ghost;
1076 if (mtv && mtv->midi_view()) {
1077 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1078 to allow having midi notes on top of note lines and waveforms.
1080 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1081 } else {
1082 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1085 ghost->set_height ();
1086 ghost->set_duration (_region->length() / samples_per_unit);
1087 ghosts.push_back (ghost);
1089 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1090 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1091 ghost->add_note(note);
1095 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1097 return ghost;
1101 /** Begin tracking note state for successive calls to add_event
1103 void
1104 MidiRegionView::begin_write()
1106 assert(!_active_notes);
1107 _active_notes = new CanvasNote*[128];
1108 for (unsigned i=0; i < 128; ++i) {
1109 _active_notes[i] = 0;
1114 /** Destroy note state for add_event
1116 void
1117 MidiRegionView::end_write()
1119 delete[] _active_notes;
1120 _active_notes = 0;
1121 _marked_for_selection.clear();
1122 _marked_for_velocity.clear();
1126 /** Resolve an active MIDI note (while recording).
1128 void
1129 MidiRegionView::resolve_note(uint8_t note, double end_time)
1131 if (midi_view()->note_mode() != Sustained) {
1132 return;
1135 if (_active_notes && _active_notes[note]) {
1136 const nframes64_t end_time_frames = beats_to_frames(end_time);
1137 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1138 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1139 _active_notes[note] = 0;
1144 /** Extend active notes to rightmost edge of region (if length is changed)
1146 void
1147 MidiRegionView::extend_active_notes()
1149 if (!_active_notes) {
1150 return;
1153 for (unsigned i=0; i < 128; ++i) {
1154 if (_active_notes[i]) {
1155 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1160 void
1161 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1163 if (no_sound_notes || !trackview.editor().sound_notes()) {
1164 return;
1167 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1168 assert(route_ui);
1170 route_ui->midi_track()->write_immediate_event(
1171 note->on_event().size(), note->on_event().buffer());
1173 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1174 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1175 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1176 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1177 note_length_ms, G_PRIORITY_DEFAULT);
1180 bool
1181 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1183 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1184 assert(route_ui);
1186 route_ui->midi_track()->write_immediate_event(
1187 note->off_event().size(), note->off_event().buffer());
1189 return false;
1192 bool
1193 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1195 const nframes64_t note_start_frames = beats_to_frames(note->time());
1197 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1198 (note_start_frames < _region->start());
1200 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1201 (note->note() <= midi_stream_view()->highest_note());
1203 return !outside;
1206 void
1207 MidiRegionView::update_note (CanvasNote* ev)
1209 boost::shared_ptr<NoteType> note = ev->note();
1211 const nframes64_t note_start_frames = beats_to_frames(note->time());
1212 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1214 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1215 const double y1 = midi_stream_view()->note_to_y(note->note());
1216 const double note_endpixel =
1217 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1219 ev->property_x1() = x;
1220 ev->property_y1() = y1;
1221 if (note->length() > 0) {
1222 ev->property_x2() = note_endpixel;
1223 } else {
1224 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1226 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1228 if (note->length() == 0) {
1229 if (_active_notes) {
1230 assert(note->note() < 128);
1231 // If this note is already active there's a stuck note,
1232 // finish the old note rectangle
1233 if (_active_notes[note->note()]) {
1234 CanvasNote* const old_rect = _active_notes[note->note()];
1235 boost::shared_ptr<NoteType> old_note = old_rect->note();
1236 old_rect->property_x2() = x;
1237 old_rect->property_outline_what() = (guint32) 0xF;
1239 _active_notes[note->note()] = ev;
1241 /* outline all but right edge */
1242 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1243 } else {
1244 /* outline all edges */
1245 ev->property_outline_what() = (guint32) 0xF;
1249 void
1250 MidiRegionView::update_hit (CanvasHit* ev)
1252 boost::shared_ptr<NoteType> note = ev->note();
1254 const nframes64_t note_start_frames = beats_to_frames(note->time());
1255 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1256 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1257 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1259 ev->move_to (x, y);
1262 /** Add a MIDI note to the view (with length).
1264 * If in sustained mode, notes with length 0 will be considered active
1265 * notes, and resolve_note should be called when the corresponding note off
1266 * event arrives, to properly display the note.
1268 void
1269 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1271 CanvasNoteEvent* event = 0;
1273 assert(note->time() >= 0);
1274 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1276 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1278 if (midi_view()->note_mode() == Sustained) {
1280 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1282 update_note (ev_rect);
1284 event = ev_rect;
1286 MidiGhostRegion* gr;
1288 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1289 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1290 gr->add_note(ev_rect);
1294 } else if (midi_view()->note_mode() == Percussive) {
1296 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1298 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1300 update_hit (ev_diamond);
1302 event = ev_diamond;
1304 } else {
1305 event = 0;
1308 if (event) {
1309 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1310 note_selected(event, true);
1313 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1314 event->show_velocity();
1316 event->on_channel_selection_change(_last_channel_selection);
1317 _events.push_back(event);
1319 if (visible) {
1320 event->show();
1321 } else {
1322 event->hide ();
1327 void
1328 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1329 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1331 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1333 start_delta_command (_("step add"));
1334 delta_add_note (new_note, true, false);
1335 apply_delta();
1337 /* potentially extend region to hold new note */
1339 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1340 nframes64_t region_end = _region->position() + _region->length() - 1;
1342 if (end_frame > region_end) {
1343 _region->set_length (end_frame, this);
1344 } else {
1345 redisplay_model ();
1349 void
1350 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1352 assert(program.time >= 0);
1354 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1355 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1357 double height = midi_stream_view()->contents_height();
1359 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1360 new CanvasProgramChange(*this, *group,
1361 displaytext,
1362 height,
1363 x, 1.0,
1364 _model_name,
1365 _custom_device_mode,
1366 program.time, program.channel, program.value));
1368 // Show unless program change is beyond the region bounds
1369 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1370 pgm_change->hide();
1371 } else {
1372 pgm_change->show();
1375 _pgm_changes.push_back(pgm_change);
1378 void
1379 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1381 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1382 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1383 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1384 float msb = -1.0;
1385 if (msb_control != 0) {
1386 msb = int(msb_control->get_float(true, time));
1387 cerr << "got msb " << msb;
1390 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1391 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1392 float lsb = -1.0;
1393 if (lsb_control != 0) {
1394 lsb = lsb_control->get_float(true, time);
1395 cerr << " got lsb " << lsb;
1398 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1399 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1400 float program_number = -1.0;
1401 if (program_control != 0) {
1402 program_number = program_control->get_float(true, time);
1403 cerr << " got program " << program_number << endl;
1406 key.msb = (int) floor(msb + 0.5);
1407 key.lsb = (int) floor(lsb + 0.5);
1408 key.program_number = (int) floor(program_number + 0.5);
1409 assert(key.is_sane());
1413 void
1414 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1416 // TODO: Get the real event here and alter them at the original times
1417 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1418 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1419 if (msb_control != 0) {
1420 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1423 // TODO: Get the real event here and alter them at the original times
1424 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1425 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1426 if (lsb_control != 0) {
1427 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1430 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1431 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1433 assert(program_control != 0);
1434 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1436 redisplay_model();
1439 void
1440 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1442 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1443 alter_program_change(program_change_event, new_patch);
1446 void
1447 MidiRegionView::previous_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().previous_patch(
1454 _model_name,
1455 _custom_device_mode,
1456 program.channel(),
1457 key);
1459 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1460 if (patch) {
1461 alter_program_change(program_change_event, patch->patch_primary_key());
1465 void
1466 MidiRegionView::next_program(CanvasProgramChange& program)
1468 MIDI::Name::PatchPrimaryKey key;
1469 get_patch_key_at(program.event_time(), program.channel(), key);
1471 boost::shared_ptr<MIDI::Name::Patch> patch =
1472 MIDI::Name::MidiPatchManager::instance().next_patch(
1473 _model_name,
1474 _custom_device_mode,
1475 program.channel(),
1476 key);
1478 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1479 if (patch) {
1480 alter_program_change(program_change_event, patch->patch_primary_key());
1484 void
1485 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1487 if (_selection.empty()) {
1488 return;
1491 if (_selection.erase (cne) > 0) {
1492 cerr << "Erased a CNE from selection\n";
1496 void
1497 MidiRegionView::delete_selection()
1499 if (_selection.empty()) {
1500 return;
1503 start_delta_command (_("delete selection"));
1505 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1506 if ((*i)->selected()) {
1507 _delta_command->remove((*i)->note());
1511 _selection.clear();
1513 apply_delta ();
1516 void
1517 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1519 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1520 if ((*i)->selected() && (*i) != ev) {
1521 (*i)->selected(false);
1522 (*i)->hide_velocity();
1526 _selection.clear();
1529 void
1530 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1532 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1533 if ((*i) != ev) {
1535 Selection::iterator tmp = i;
1536 ++tmp;
1538 (*i)->selected (false);
1539 _selection.erase (i);
1541 i = tmp;
1543 } else {
1544 ++i;
1548 /* don't bother with removing this regionview from the editor selection,
1549 since we're about to add another note, and thus put/keep this
1550 regionview in the editor selection.
1553 if (!ev->selected()) {
1554 add_to_selection (ev);
1558 void
1559 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1561 uint8_t low_note = 127;
1562 uint8_t high_note = 0;
1563 MidiModel::Notes& notes (_model->notes());
1564 _optimization_iterator = _events.begin();
1566 if (extend && _selection.empty()) {
1567 extend = false;
1570 if (extend) {
1572 /* scan existing selection to get note range */
1574 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1575 if ((*i)->note()->note() < low_note) {
1576 low_note = (*i)->note()->note();
1578 if ((*i)->note()->note() > high_note) {
1579 high_note = (*i)->note()->note();
1583 low_note = min (low_note, notenum);
1584 high_note = max (high_note, notenum);
1587 no_sound_notes = true;
1589 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1591 boost::shared_ptr<NoteType> note (*n);
1592 CanvasNoteEvent* cne;
1593 bool select = false;
1595 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1596 if (extend) {
1597 if ((note->note() >= low_note && note->note() <= high_note)) {
1598 select = true;
1600 } else if (note->note() == notenum) {
1601 select = true;
1605 if (select) {
1606 if ((cne = find_canvas_note (note)) != 0) {
1607 // extend is false because we've taken care of it,
1608 // since it extends by time range, not pitch.
1609 note_selected (cne, add, false);
1613 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1617 no_sound_notes = false;
1620 void
1621 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1623 MidiModel::Notes& notes (_model->notes());
1624 _optimization_iterator = _events.begin();
1626 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1628 boost::shared_ptr<NoteType> note (*n);
1629 CanvasNoteEvent* cne;
1631 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1632 if ((cne = find_canvas_note (note)) != 0) {
1633 if (cne->selected()) {
1634 note_deselected (cne);
1635 } else {
1636 note_selected (cne, true, false);
1643 void
1644 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1646 if (!add) {
1647 clear_selection_except(ev);
1650 if (!extend) {
1652 if (!ev->selected()) {
1653 add_to_selection (ev);
1656 } else {
1657 /* find end of latest note selected, select all between that and the start of "ev" */
1659 Evoral::MusicalTime earliest = DBL_MAX;
1660 Evoral::MusicalTime latest = 0;
1662 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1663 if ((*i)->note()->end_time() > latest) {
1664 latest = (*i)->note()->end_time();
1666 if ((*i)->note()->time() < earliest) {
1667 earliest = (*i)->note()->time();
1671 if (ev->note()->end_time() > latest) {
1672 latest = ev->note()->end_time();
1675 if (ev->note()->time() < earliest) {
1676 earliest = ev->note()->time();
1679 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1681 /* find notes entirely within OR spanning the earliest..latest range */
1683 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1684 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1685 add_to_selection (*i);
1688 #if 0
1689 /* if events were guaranteed to be time sorted, we could do this.
1690 but as of sept 10th 2009, they no longer are.
1693 if ((*i)->note()->time() > latest) {
1694 break;
1696 #endif
1701 void
1702 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1704 remove_from_selection (ev);
1707 void
1708 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1710 if (x1 > x2) {
1711 swap (x1, x2);
1714 if (y1 > y2) {
1715 swap (y1, y2);
1718 // TODO: Make this faster by storing the last updated selection rect, and only
1719 // adjusting things that are in the area that appears/disappeared.
1720 // We probably need a tree to be able to find events in O(log(n)) time.
1722 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1724 /* check if any corner of the note is inside the rect
1726 Notes:
1727 1) this is computing "touched by", not "contained by" the rect.
1728 2) this does not require that events be sorted in time.
1731 const double ix1 = (*i)->x1();
1732 const double ix2 = (*i)->x2();
1733 const double iy1 = (*i)->y1();
1734 const double iy2 = (*i)->y2();
1736 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1737 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1738 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1739 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1741 // Inside rectangle
1742 if (!(*i)->selected()) {
1743 add_to_selection (*i);
1745 } else if ((*i)->selected()) {
1746 // Not inside rectangle
1747 remove_from_selection (*i);
1752 void
1753 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1755 Selection::iterator i = _selection.find (ev);
1757 if (i != _selection.end()) {
1758 _selection.erase (i);
1761 ev->selected (false);
1762 ev->hide_velocity ();
1764 if (_selection.empty()) {
1765 PublicEditor& editor (trackview.editor());
1766 editor.get_selection().remove (this);
1770 void
1771 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1773 bool add_mrv_selection = false;
1775 if (_selection.empty()) {
1776 add_mrv_selection = true;
1779 if (_selection.insert (ev).second) {
1780 ev->selected (true);
1781 play_midi_note ((ev)->note());
1784 if (add_mrv_selection) {
1785 PublicEditor& editor (trackview.editor());
1786 editor.get_selection().add (this);
1790 void
1791 MidiRegionView::move_selection(double dx, double dy)
1793 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1794 (*i)->move_event(dx, dy);
1798 void
1799 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1801 assert (!_selection.empty());
1803 uint8_t lowest_note_in_selection = 127;
1804 uint8_t highest_note_in_selection = 0;
1805 uint8_t highest_note_difference = 0;
1807 // find highest and lowest notes first
1809 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1810 uint8_t pitch = (*i)->note()->note();
1811 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1812 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1816 cerr << "dnote: " << (int) dnote << endl;
1817 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1818 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1819 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1820 << int(highest_note_in_selection) << endl;
1821 cerr << "selection size: " << _selection.size() << endl;
1822 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1825 // Make sure the note pitch does not exceed the MIDI standard range
1826 if (highest_note_in_selection + dnote > 127) {
1827 highest_note_difference = highest_note_in_selection - 127;
1830 start_diff_command(_("move notes"));
1832 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1834 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1836 if (dt >= 0) {
1837 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1838 } else {
1839 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1842 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1844 if (new_time < 0) {
1845 continue;
1848 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1850 uint8_t original_pitch = (*i)->note()->note();
1851 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1853 // keep notes in standard midi range
1854 clamp_to_0_127(new_pitch);
1856 // keep original pitch if note is dragged outside valid midi range
1857 if ((original_pitch != 0 && new_pitch == 0)
1858 || (original_pitch != 127 && new_pitch == 127)) {
1859 new_pitch = original_pitch;
1862 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1863 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1865 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1868 apply_diff();
1870 // care about notes being moved beyond the upper/lower bounds on the canvas
1871 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1872 highest_note_in_selection > midi_stream_view()->highest_note()) {
1873 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1877 nframes64_t
1878 MidiRegionView::snap_pixel_to_frame(double x)
1880 PublicEditor& editor = trackview.editor();
1881 // x is region relative, convert it to global absolute frames
1882 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1883 editor.snap_to(frame);
1884 return frame - _region->position(); // convert back to region relative
1887 nframes64_t
1888 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1890 PublicEditor& editor = trackview.editor();
1891 // x is region relative, convert it to global absolute frames
1892 nframes64_t frame = x + _region->position();
1893 editor.snap_to(frame);
1894 return frame - _region->position(); // convert back to region relative
1897 double
1898 MidiRegionView::snap_to_pixel(double x)
1900 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1903 double
1904 MidiRegionView::get_position_pixels()
1906 nframes64_t region_frame = get_position();
1907 return trackview.editor().frame_to_pixel(region_frame);
1910 double
1911 MidiRegionView::get_end_position_pixels()
1913 nframes64_t frame = get_position() + get_duration ();
1914 return trackview.editor().frame_to_pixel(frame);
1917 nframes64_t
1918 MidiRegionView::beats_to_frames(double beats) const
1920 return _time_converter.to(beats);
1923 double
1924 MidiRegionView::frames_to_beats(nframes64_t frames) const
1926 return _time_converter.from(frames);
1929 void
1930 MidiRegionView::begin_resizing (bool /*at_front*/)
1932 _resize_data.clear();
1934 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1935 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1937 // only insert CanvasNotes into the map
1938 if (note) {
1939 NoteResizeData *resize_data = new NoteResizeData();
1940 resize_data->canvas_note = note;
1942 // create a new SimpleRect from the note which will be the resize preview
1943 SimpleRect *resize_rect = new SimpleRect(
1944 *group, note->x1(), note->y1(), note->x2(), note->y2());
1946 // calculate the colors: get the color settings
1947 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1948 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1949 128);
1951 // make the resize preview notes more transparent and bright
1952 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1954 // calculate color based on note velocity
1955 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1956 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1957 fill_color,
1958 0.85);
1960 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1961 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1963 resize_data->resize_rect = resize_rect;
1964 _resize_data.push_back(resize_data);
1969 void
1970 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1972 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1973 SimpleRect* resize_rect = (*i)->resize_rect;
1974 CanvasNote* canvas_note = (*i)->canvas_note;
1975 double current_x;
1977 if (at_front) {
1978 if (relative) {
1979 current_x = canvas_note->x1() + delta_x;
1980 } else {
1981 // x is in track relative, transform it to region relative
1982 current_x = delta_x - get_position_pixels();
1984 } else {
1985 if (relative) {
1986 current_x = canvas_note->x2() + delta_x;
1987 } else {
1988 // x is in track relative, transform it to region relative
1989 current_x = delta_x - get_end_position_pixels ();
1993 if (at_front) {
1994 resize_rect->property_x1() = snap_to_pixel(current_x);
1995 resize_rect->property_x2() = canvas_note->x2();
1996 } else {
1997 resize_rect->property_x2() = snap_to_pixel(current_x);
1998 resize_rect->property_x1() = canvas_note->x1();
2003 void
2004 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
2006 start_diff_command(_("resize notes"));
2008 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2009 CanvasNote* canvas_note = (*i)->canvas_note;
2010 SimpleRect* resize_rect = (*i)->resize_rect;
2011 const double region_start = get_position_pixels();
2012 double current_x;
2014 if (at_front) {
2015 if (relative) {
2016 current_x = canvas_note->x1() + delta_x;
2017 } else {
2018 // x is in track relative, transform it to region relative
2019 current_x = region_start + delta_x;
2021 } else {
2022 if (relative) {
2023 current_x = canvas_note->x2() + delta_x;
2024 } else {
2025 // x is in track relative, transform it to region relative
2026 current_x = region_start + delta_x;
2030 current_x = snap_pixel_to_frame (current_x);
2031 current_x = frames_to_beats (current_x);
2033 if (at_front && current_x < canvas_note->note()->end_time()) {
2034 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2036 double len = canvas_note->note()->time() - current_x;
2037 len += canvas_note->note()->length();
2039 if (len > 0) {
2040 /* XXX convert to beats */
2041 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2045 if (!at_front) {
2046 double len = current_x - canvas_note->note()->time();
2048 if (len > 0) {
2049 /* XXX convert to beats */
2050 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2054 delete resize_rect;
2055 delete (*i);
2058 _resize_data.clear();
2059 apply_diff();
2062 void
2063 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2065 uint8_t new_velocity;
2067 if (relative) {
2068 new_velocity = event->note()->velocity() + velocity;
2069 clamp_to_0_127(new_velocity);
2070 } else {
2071 new_velocity = velocity;
2074 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2077 void
2078 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2080 uint8_t new_note;
2082 if (relative) {
2083 new_note = event->note()->note() + note;
2084 } else {
2085 new_note = note;
2088 clamp_to_0_127 (new_note);
2089 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2092 void
2093 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2095 bool change_start = false;
2096 bool change_length = false;
2097 Evoral::MusicalTime new_start;
2098 Evoral::MusicalTime new_length;
2100 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2102 front_delta: if positive - move the start of the note later in time (shortening it)
2103 if negative - move the start of the note earlier in time (lengthening it)
2105 end_delta: if positive - move the end of the note later in time (lengthening it)
2106 if negative - move the end of the note earlier in time (shortening it)
2109 if (front_delta) {
2110 if (front_delta < 0) {
2112 if (event->note()->time() < -front_delta) {
2113 new_start = 0;
2114 } else {
2115 new_start = event->note()->time() + front_delta; // moves earlier
2118 /* start moved toward zero, so move the end point out to where it used to be.
2119 Note that front_delta is negative, so this increases the length.
2122 new_length = event->note()->length() - front_delta;
2123 change_start = true;
2124 change_length = true;
2126 } else {
2128 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2130 if (new_pos < event->note()->end_time()) {
2131 new_start = event->note()->time() + front_delta;
2132 /* start moved toward the end, so move the end point back to where it used to be */
2133 new_length = event->note()->length() - front_delta;
2134 change_start = true;
2135 change_length = true;
2141 if (end_delta) {
2142 bool can_change = true;
2143 if (end_delta < 0) {
2144 if (event->note()->length() < -end_delta) {
2145 can_change = false;
2149 if (can_change) {
2150 new_length = event->note()->length() + end_delta;
2151 change_length = true;
2155 if (change_start) {
2156 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2159 if (change_length) {
2160 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2164 void
2165 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2167 Evoral::MusicalTime new_time;
2169 if (relative) {
2170 if (delta < 0.0) {
2171 if (event->note()->time() < -delta) {
2172 new_time = 0;
2173 } else {
2174 new_time = event->note()->time() + delta;
2176 } else {
2177 new_time = event->note()->time() + delta;
2179 } else {
2180 new_time = delta;
2183 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2186 void
2187 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2189 int8_t delta;
2191 if (_selection.empty()) {
2192 return;
2195 if (fine) {
2196 delta = 1;
2197 } else {
2198 delta = 10;
2201 if (!up) {
2202 delta = -delta;
2205 if (!allow_smush) {
2206 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2207 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2208 return;
2213 start_diff_command(_("change velocities"));
2215 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2216 Selection::iterator next = i;
2217 ++next;
2218 change_note_velocity (*i, delta, true);
2219 i = next;
2222 apply_diff();
2226 void
2227 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2229 if (_selection.empty()) {
2230 return;
2233 int8_t delta;
2235 if (fine) {
2236 delta = 1;
2237 } else {
2238 delta = 12;
2241 if (!up) {
2242 delta = -delta;
2245 if (!allow_smush) {
2246 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2247 if (!up) {
2248 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2249 return;
2251 } else {
2252 if ((int8_t) (*i)->note()->note() + delta > 127) {
2253 return;
2259 start_diff_command (_("transpose"));
2261 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2262 Selection::iterator next = i;
2263 ++next;
2264 change_note_note (*i, delta, true);
2265 i = next;
2268 apply_diff ();
2271 void
2272 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2274 Evoral::MusicalTime delta;
2276 if (fine) {
2277 delta = 1.0/128.0;
2278 } else {
2279 /* grab the current grid distance */
2280 bool success;
2281 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2282 if (!success) {
2283 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2284 cerr << "Grid type not available as beats - TO BE FIXED\n";
2285 return;
2289 if (shorter) {
2290 delta = -delta;
2293 start_diff_command (_("change note lengths"));
2295 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2296 Selection::iterator next = i;
2297 ++next;
2299 /* note the negation of the delta for start */
2301 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2302 i = next;
2305 apply_diff ();
2309 void
2310 MidiRegionView::nudge_notes (bool forward)
2312 if (_selection.empty()) {
2313 return;
2316 /* pick a note as the point along the timeline to get the nudge distance.
2317 its not necessarily the earliest note, so we may want to pull the notes out
2318 into a vector and sort before using the first one.
2321 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2322 nframes64_t unused;
2323 nframes64_t distance;
2325 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2327 /* grid is off - use nudge distance */
2329 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2331 } else {
2333 /* use grid */
2335 nframes64_t next_pos = ref_point;
2337 if (forward) {
2338 /* XXX need check on max_frames, but that needs max_frames64 or something */
2339 next_pos += 1;
2340 } else {
2341 if (next_pos == 0) {
2342 return;
2344 next_pos -= 1;
2347 cerr << "ref point was " << ref_point << " next was " << next_pos;
2348 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2349 distance = ref_point - next_pos;
2350 cerr << " final is " << next_pos << " distance = " << distance << endl;
2353 if (distance == 0) {
2354 return;
2357 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2359 if (!forward) {
2360 delta = -delta;
2363 start_diff_command (_("nudge"));
2365 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2366 Selection::iterator next = i;
2367 ++next;
2368 change_note_time (*i, delta, true);
2369 i = next;
2372 apply_diff ();
2375 void
2376 MidiRegionView::change_channel(uint8_t channel)
2378 start_diff_command(_("change channel"));
2379 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2380 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2382 apply_diff();
2386 void
2387 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2389 if (_mouse_state == SelectTouchDragging) {
2390 note_selected(ev, true);
2393 char buf[4];
2394 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2395 // This causes an infinite loop on note add sometimes
2396 //PublicEditor& editor (trackview.editor());
2397 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2398 //editor.show_verbose_canvas_cursor_with (buf);
2401 void
2402 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2404 PublicEditor& editor (trackview.editor());
2405 editor.hide_verbose_canvas_cursor ();
2409 void
2410 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2412 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2413 if (msrc)
2414 display_model(msrc->model());
2417 void
2418 MidiRegionView::set_frame_color()
2420 if (frame) {
2421 if (_selected && should_show_selection) {
2422 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2423 } else {
2424 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2429 void
2430 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2432 switch (mode) {
2433 case AllChannels:
2434 case FilterChannels:
2435 _force_channel = -1;
2436 break;
2437 case ForceChannel:
2438 _force_channel = mask;
2439 mask = 0xFFFF; // Show all notes as active (below)
2442 // Update notes for selection
2443 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2444 (*i)->on_channel_selection_change(mask);
2447 _last_channel_selection = mask;
2450 void
2451 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2453 _model_name = model;
2454 _custom_device_mode = custom_device_mode;
2455 redisplay_model();
2458 void
2459 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2461 if (_selection.empty()) {
2462 return;
2465 PublicEditor& editor (trackview.editor());
2467 switch (op) {
2468 case Cut:
2469 case Copy:
2470 editor.get_cut_buffer().add (selection_as_cut_buffer());
2471 break;
2472 default:
2473 break;
2476 if (op != Copy) {
2478 start_delta_command();
2480 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2481 switch (op) {
2482 case Copy:
2483 break;
2484 case Cut:
2485 case Clear:
2486 delta_remove_note (*i);
2487 break;
2491 apply_delta();
2495 MidiCutBuffer*
2496 MidiRegionView::selection_as_cut_buffer () const
2498 Notes notes;
2500 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2501 NoteType* n = (*i)->note().get();
2502 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2505 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2506 cb->set (notes);
2508 return cb;
2511 void
2512 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2514 if (mcb.empty()) {
2515 return;
2518 start_delta_command (_("paste"));
2520 Evoral::MusicalTime beat_delta;
2521 Evoral::MusicalTime paste_pos_beats;
2522 Evoral::MusicalTime duration;
2523 Evoral::MusicalTime end_point;
2525 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2526 paste_pos_beats = frames_to_beats (pos - _region->position());
2527 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2528 paste_pos_beats = 0;
2530 _selection.clear ();
2532 for (int n = 0; n < (int) times; ++n) {
2534 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2536 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2538 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2539 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2541 /* make all newly added notes selected */
2543 delta_add_note (copied_note, true);
2544 end_point = copied_note->end_time();
2547 paste_pos_beats += duration;
2550 /* if we pasted past the current end of the region, extend the region */
2552 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2553 nframes64_t region_end = _region->position() + _region->length() - 1;
2555 if (end_frame > region_end) {
2557 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2559 trackview.session()->begin_reversible_command (_("paste"));
2561 _region->clear_history ();
2562 _region->set_length (end_frame, this);
2563 trackview.session()->add_command (new StatefulDiffCommand (_region));
2566 cerr << "region end finally at " << _region->position() + _region->length() - 1;
2567 apply_delta ();
2570 struct EventNoteTimeEarlyFirstComparator {
2571 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2572 return a->note()->time() < b->note()->time();
2576 void
2577 MidiRegionView::time_sort_events ()
2579 if (!_sort_needed) {
2580 return;
2583 EventNoteTimeEarlyFirstComparator cmp;
2584 _events.sort (cmp);
2586 _sort_needed = false;
2589 void
2590 MidiRegionView::goto_next_note ()
2592 // nframes64_t pos = -1;
2593 bool use_next = false;
2595 if (_events.back()->selected()) {
2596 return;
2599 time_sort_events ();
2601 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2602 if ((*i)->selected()) {
2603 use_next = true;
2604 continue;
2605 } else if (use_next) {
2606 unique_select (*i);
2607 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2608 return;
2612 /* use the first one */
2614 unique_select (_events.front());
2618 void
2619 MidiRegionView::goto_previous_note ()
2621 // nframes64_t pos = -1;
2622 bool use_next = false;
2624 if (_events.front()->selected()) {
2625 return;
2628 time_sort_events ();
2630 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2631 if ((*i)->selected()) {
2632 use_next = true;
2633 continue;
2634 } else if (use_next) {
2635 unique_select (*i);
2636 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2637 return;
2641 /* use the last one */
2643 unique_select (*(_events.rbegin()));
2646 void
2647 MidiRegionView::selection_as_notelist (Notes& selected)
2649 time_sort_events ();
2651 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2652 if ((*i)->selected()) {
2653 selected.insert ((*i)->note());