2 Copyright (C) 2001-2007 Paul Davis
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_diskstream.h"
39 #include "ardour/midi_model.h"
40 #include "ardour/midi_patch_manager.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/Control.hpp"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas-program-change.h"
51 #include "ghostregion.h"
52 #include "gui_thread.h"
54 #include "midi_cut_buffer.h"
55 #include "midi_list_editor.h"
56 #include "midi_region_view.h"
57 #include "midi_streamview.h"
58 #include "midi_time_axis.h"
59 #include "midi_time_axis.h"
60 #include "midi_util.h"
61 #include "public_editor.h"
62 #include "selection.h"
63 #include "simpleline.h"
64 #include "streamview.h"
69 using namespace ARDOUR;
71 using namespace Editing;
72 using namespace ArdourCanvas;
73 using Gtkmm2ext::Keyboard;
75 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
76 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
77 : RegionView (parent, tv, r, spu, basic_color)
79 , _last_channel_selection(0xFFFF)
80 , _default_note_length(1.0)
81 , _current_range_min(0)
82 , _current_range_max(0)
83 , _model_name(string())
84 , _custom_device_mode(string())
86 , _note_group(new ArdourCanvas::Group(*parent))
92 , _optimization_iterator (_events.end())
94 , no_sound_notes (false)
96 _note_group->raise_to_top();
99 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
100 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
101 TimeAxisViewItem::Visibility visibility)
102 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
104 , _last_channel_selection(0xFFFF)
105 , _default_note_length(1.0)
106 , _model_name(string())
107 , _custom_device_mode(string())
109 , _note_group(new ArdourCanvas::Group(*parent))
114 , _sort_needed (true)
115 , _optimization_iterator (_events.end())
117 , no_sound_notes (false)
120 _note_group->raise_to_top();
124 MidiRegionView::MidiRegionView (const MidiRegionView& other)
125 : sigc::trackable(other)
128 , _last_channel_selection(0xFFFF)
129 , _default_note_length(1.0)
130 , _model_name(string())
131 , _custom_device_mode(string())
133 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
138 , _sort_needed (true)
139 , _optimization_iterator (_events.end())
141 , no_sound_notes (false)
146 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
147 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
152 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
153 : RegionView (other, boost::shared_ptr<Region> (region))
155 , _last_channel_selection(0xFFFF)
156 , _default_note_length(1.0)
157 , _model_name(string())
158 , _custom_device_mode(string())
160 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
165 , _sort_needed (true)
166 , _optimization_iterator (_events.end())
168 , no_sound_notes (false)
173 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
174 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
180 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
183 midi_region()->midi_source(0)->load_model();
186 _model = midi_region()->midi_source(0)->model();
187 _enable_display = false;
189 RegionView::init (basic_color, false);
191 compute_colors (basic_color);
193 set_height (trackview.current_height());
196 region_sync_changed ();
197 region_resized (ARDOUR::bounds_change);
200 reset_width_dependent_items (_pixel_width);
204 _enable_display = true;
207 display_model (_model);
211 group->raise_to_top();
212 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
214 midi_view()->signal_channel_mode_changed().connect(
215 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
217 midi_view()->signal_midi_patch_settings_changed().connect(
218 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
222 MidiRegionView::canvas_event(GdkEvent* ev)
224 PublicEditor& editor (trackview.editor());
226 if (!editor.internal_editing()) {
230 static double drag_start_x, drag_start_y;
231 static double last_x, last_y;
232 double event_x, event_y;
233 nframes64_t event_frame = 0;
236 static ArdourCanvas::SimpleRect* drag_rect = 0;
238 /* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
239 to its items, which means that ev->type == GDK_SCROLL will never be seen
244 fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
246 if (ev->scroll.direction == GDK_SCROLL_UP) {
247 change_velocities (true, fine, false);
249 } else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
250 change_velocities (false, fine, false);
259 /* since GTK bindings are generally activated on press, and since
260 detectable auto-repeat is the name of the game and only sends
261 repeated presses, carry out key actions at key press, not release.
264 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
265 _mouse_state = SelectTouchDragging;
268 } else if (ev->key.keyval == GDK_Escape) {
272 } else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
274 bool start = (ev->key.keyval == GDK_comma);
275 bool end = (ev->key.keyval == GDK_period);
276 bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
277 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
279 change_note_lengths (fine, shorter, start, end);
283 } else if (ev->key.keyval == GDK_Delete) {
288 } else if (ev->key.keyval == GDK_Tab) {
290 if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
291 goto_previous_note ();
297 } else if (ev->key.keyval == GDK_Up) {
299 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
300 bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
302 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
303 change_velocities (true, fine, allow_smush);
305 transpose (true, fine, allow_smush);
309 } else if (ev->key.keyval == GDK_Down) {
311 bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
312 fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
314 if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
315 change_velocities (false, fine, allow_smush);
317 transpose (false, fine, allow_smush);
321 } else if (ev->key.keyval == GDK_Left) {
326 } else if (ev->key.keyval == GDK_Right) {
331 } else if (ev->key.keyval == GDK_Control_L) {
334 } else if (ev->key.keyval == GDK_r) {
335 /* if we're not step editing, this really doesn't matter */
336 midi_view()->step_edit_rest ();
342 case GDK_KEY_RELEASE:
343 if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
349 case GDK_BUTTON_PRESS:
350 if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
351 _pressed_button = ev->button.button;
352 _mouse_state = Pressed;
355 _pressed_button = ev->button.button;
358 case GDK_2BUTTON_PRESS:
361 case GDK_ENTER_NOTIFY:
362 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
363 Keyboard::magic_widget_grab_focus();
367 case GDK_MOTION_NOTIFY:
368 event_x = ev->motion.x;
369 event_y = ev->motion.y;
370 group->w2i(event_x, event_y);
372 // convert event_x to global frame
373 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
374 trackview.editor().snap_to(event_frame);
375 // convert event_frame back to local coordinates relative to position
376 event_frame -= _region->position();
378 switch (_mouse_state) {
379 case Pressed: // Drag start
382 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
383 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
384 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
387 drag_start_x = event_x;
388 drag_start_y = event_y;
390 drag_rect = new ArdourCanvas::SimpleRect(*group);
391 drag_rect->property_x1() = event_x;
392 drag_rect->property_y1() = event_y;
393 drag_rect->property_x2() = event_x;
394 drag_rect->property_y2() = event_y;
395 drag_rect->property_outline_what() = 0xFF;
396 drag_rect->property_outline_color_rgba()
397 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
398 drag_rect->property_fill_color_rgba()
399 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
401 _mouse_state = SelectRectDragging;
404 // Add note drag start
405 } else if (editor.current_mouse_mode() == MouseRange) {
406 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
407 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
410 drag_start_x = event_x;
411 drag_start_y = event_y;
413 drag_rect = new ArdourCanvas::SimpleRect(*group);
414 drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
416 drag_rect->property_y1() = midi_stream_view()->note_to_y(
417 midi_stream_view()->y_to_note(event_y));
418 drag_rect->property_x2() = event_x;
419 drag_rect->property_y2() = drag_rect->property_y1()
420 + floor(midi_stream_view()->note_height());
421 drag_rect->property_outline_what() = 0xFF;
422 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
423 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
425 _mouse_state = AddDragging;
431 case SelectRectDragging: // Select drag motion
432 case AddDragging: // Add note drag motion
433 if (ev->motion.is_hint) {
436 GdkModifierType state;
437 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
442 if (_mouse_state == AddDragging)
443 event_x = trackview.editor().frame_to_pixel(event_frame);
446 if (event_x > drag_start_x)
447 drag_rect->property_x2() = event_x;
449 drag_rect->property_x1() = event_x;
452 if (drag_rect && _mouse_state == SelectRectDragging) {
453 if (event_y > drag_start_y)
454 drag_rect->property_y2() = event_y;
456 drag_rect->property_y1() = event_y;
458 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
464 case SelectTouchDragging:
472 case GDK_BUTTON_RELEASE:
473 event_x = ev->motion.x;
474 event_y = ev->motion.y;
475 group->w2i(event_x, event_y);
476 group->ungrab(ev->button.time);
477 event_frame = trackview.editor().pixel_to_frame(event_x);
479 if (ev->button.button == 3) {
481 } else if (_pressed_button != 1) {
485 switch (_mouse_state) {
486 case Pressed: // Clicked
487 switch (editor.current_mouse_mode()) {
493 create_note_at(event_x, event_y, _default_note_length);
500 case SelectRectDragging: // Select drag done
505 case AddDragging: // Add drag done
507 if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
508 const double x = drag_rect->property_x1();
509 const double length = trackview.editor().pixel_to_frame(
510 drag_rect->property_x2() - drag_rect->property_x1());
512 create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
527 MidiRegionView::show_list_editor ()
530 _list_editor = new MidiListEditor (trackview.session(), midi_region());
532 _list_editor->present ();
535 /** Add a note to the model, and the view, at a canvas (click) coordinate.
536 * \param x horizontal position in pixels
537 * \param y vertical position in pixels
538 * \param length duration of the note in beats */
540 MidiRegionView::create_note_at(double x, double y, double length)
542 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
543 MidiStreamView* const view = mtv->midi_view();
545 double note = midi_stream_view()->y_to_note(y);
548 assert(note <= 127.0);
550 // Start of note in frames relative to region start
551 nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
552 assert(start_frames >= 0);
555 length = frames_to_beats(
556 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
558 const boost::shared_ptr<NoteType> new_note(new NoteType(0,
559 frames_to_beats(start_frames + _region->start()), length,
560 (uint8_t)note, 0x40));
562 view->update_note_range(new_note->note());
564 MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
566 _model->apply_command(*trackview.session(), cmd);
568 play_midi_note (new_note);
572 MidiRegionView::clear_events()
577 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
578 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
583 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
588 _pgm_changes.clear();
590 _optimization_iterator = _events.end();
595 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
598 content_connection.disconnect ();
599 _model->ContentsChanged.connect (content_connection, boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
603 if (_enable_display) {
610 MidiRegionView::start_delta_command(string name)
612 if (!_delta_command) {
613 _delta_command = _model->new_delta_command(name);
618 MidiRegionView::start_diff_command(string name)
620 if (!_diff_command) {
621 _diff_command = _model->new_diff_command(name);
626 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
628 if (_delta_command) {
629 _delta_command->add(note);
632 _marked_for_selection.insert(note);
635 _marked_for_velocity.insert(note);
640 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
642 if (_delta_command && ev->note()) {
643 _delta_command->remove(ev->note());
648 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
649 MidiModel::DiffCommand::Property property,
653 _diff_command->change (ev->note(), property, val);
658 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
659 MidiModel::DiffCommand::Property property,
660 Evoral::MusicalTime val)
663 _diff_command->change (ev->note(), property, val);
668 MidiRegionView::apply_delta()
670 if (!_delta_command) {
674 // Mark all selected notes for selection when model reloads
675 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
676 _marked_for_selection.insert((*i)->note());
679 _model->apply_command(*trackview.session(), _delta_command);
681 midi_view()->midi_track()->diskstream()->playlist_modified();
683 _marked_for_selection.clear();
684 _marked_for_velocity.clear();
688 MidiRegionView::apply_diff ()
690 if (!_diff_command) {
694 _model->apply_command(*trackview.session(), _diff_command);
696 midi_view()->midi_track()->diskstream()->playlist_modified();
698 _marked_for_velocity.clear();
702 MidiRegionView::apply_delta_as_subcommand()
704 if (!_delta_command) {
708 // Mark all selected notes for selection when model reloads
709 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
710 _marked_for_selection.insert((*i)->note());
713 _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
715 midi_view()->midi_track()->diskstream()->playlist_modified();
717 _marked_for_selection.clear();
718 _marked_for_velocity.clear();
722 MidiRegionView::apply_diff_as_subcommand()
724 if (!_diff_command) {
728 // Mark all selected notes for selection when model reloads
729 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
730 _marked_for_selection.insert((*i)->note());
733 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
735 midi_view()->midi_track()->diskstream()->playlist_modified();
737 _marked_for_selection.clear();
738 _marked_for_velocity.clear();
742 MidiRegionView::abort_command()
744 delete _delta_command;
746 delete _diff_command;
752 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
754 if (_optimization_iterator != _events.end()) {
755 ++_optimization_iterator;
758 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
759 return *_optimization_iterator;
762 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
763 if ((*_optimization_iterator)->note() == note) {
764 return *_optimization_iterator;
772 MidiRegionView::redisplay_model()
774 // Don't redisplay the model if we're currently recording and displaying that
780 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
784 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
788 MidiModel::ReadLock lock(_model->read_lock());
790 MidiModel::Notes& notes (_model->notes());
791 _optimization_iterator = _events.begin();
793 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
795 boost::shared_ptr<NoteType> note (*n);
796 CanvasNoteEvent* cne;
799 if (note_in_region_range (note, visible)) {
801 if ((cne = find_canvas_note (note)) != 0) {
808 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
810 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
822 add_note (note, visible);
827 if ((cne = find_canvas_note (note)) != 0) {
834 /* remove note items that are no longer valid */
836 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
837 if (!(*i)->valid ()) {
839 i = _events.erase (i);
846 display_program_changes();
848 _marked_for_selection.clear ();
849 _marked_for_velocity.clear ();
851 /* we may have caused _events to contain things out of order (e.g. if a note
852 moved earlier or later). we don't generally need them in time order, but
853 make a note that a sort is required for those cases that require it.
860 MidiRegionView::display_program_changes()
862 boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
867 Glib::Mutex::Lock lock (control->list()->lock());
869 uint8_t channel = control->parameter().channel();
871 for (AutomationList::const_iterator event = control->list()->begin();
872 event != control->list()->end(); ++event) {
873 double event_time = (*event)->when;
874 double program_number = floor((*event)->value + 0.5);
876 // Get current value of bank select MSB at time of the program change
877 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
878 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
880 if (msb_control != 0) {
881 msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
884 // Get current value of bank select LSB at time of the program change
885 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
886 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
888 if (lsb_control != 0) {
889 lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
892 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
894 boost::shared_ptr<MIDI::Name::Patch> patch =
895 MIDI::Name::MidiPatchManager::instance().find_patch(
896 _model_name, _custom_device_mode, channel, patch_key);
898 PCEvent program_change(event_time, uint8_t(program_number), channel);
901 add_pgm_change(program_change, patch->name());
904 snprintf(buf, 4, "%d", int(program_number));
905 add_pgm_change(program_change, buf);
911 MidiRegionView::display_sysexes()
913 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
914 Evoral::MusicalTime time = (*i)->time();
919 for (uint32_t b = 0; b < (*i)->size(); ++b) {
920 str << int((*i)->buffer()[b]);
921 if (b != (*i)->size() -1) {
925 string text = str.str();
927 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
929 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
931 double height = midi_stream_view()->contents_height();
933 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
934 new CanvasSysEx(*this, *group, text, height, x, 1.0));
936 // Show unless program change is beyond the region bounds
937 if (time - _region->start() >= _region->length() || time < _region->start()) {
943 _sys_exes.push_back(sysex);
948 MidiRegionView::~MidiRegionView ()
950 in_destructor = true;
954 RegionViewGoingAway (this); /* EMIT_SIGNAL */
963 delete _delta_command;
967 MidiRegionView::region_resized (const PropertyChange& what_changed)
969 RegionView::region_resized(what_changed);
971 if (what_changed.contains (ARDOUR::Properties::position)) {
972 set_duration(_region->length(), 0);
973 if (_enable_display) {
980 MidiRegionView::reset_width_dependent_items (double pixel_width)
982 RegionView::reset_width_dependent_items(pixel_width);
983 assert(_pixel_width == pixel_width);
985 if (_enable_display) {
991 MidiRegionView::set_height (double height)
993 static const double FUDGE = 2.0;
994 const double old_height = _height;
995 RegionView::set_height(height);
996 _height = height - FUDGE;
998 apply_note_range(midi_stream_view()->lowest_note(),
999 midi_stream_view()->highest_note(),
1000 height != old_height + FUDGE);
1003 name_pixbuf->raise_to_top();
1008 /** Apply the current note range from the stream view
1009 * by repositioning/hiding notes as necessary
1012 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1014 if (!_enable_display) {
1018 if (!force && _current_range_min == min && _current_range_max == max) {
1022 _current_range_min = min;
1023 _current_range_max = max;
1025 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1026 CanvasNoteEvent* event = *i;
1027 boost::shared_ptr<NoteType> note (event->note());
1029 if (note->note() < _current_range_min ||
1030 note->note() > _current_range_max) {
1036 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1038 const double y1 = midi_stream_view()->note_to_y(note->note());
1039 const double y2 = y1 + floor(midi_stream_view()->note_height());
1041 cnote->property_y1() = y1;
1042 cnote->property_y2() = y2;
1044 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1046 double x = trackview.editor().frame_to_pixel(
1047 beats_to_frames(note->time()) - _region->start());
1048 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1049 double y = midi_stream_view()->note_to_y(event->note()->note())
1050 + ((diamond_size-2.0) / 4.0);
1052 chit->set_height (diamond_size);
1053 chit->move (x - chit->x1(), y - chit->y1());
1060 MidiRegionView::add_ghost (TimeAxisView& tv)
1064 double unit_position = _region->position () / samples_per_unit;
1065 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1066 MidiGhostRegion* ghost;
1068 if (mtv && mtv->midi_view()) {
1069 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1070 to allow having midi notes on top of note lines and waveforms.
1072 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1074 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1077 ghost->set_height ();
1078 ghost->set_duration (_region->length() / samples_per_unit);
1079 ghosts.push_back (ghost);
1081 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1082 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1083 ghost->add_note(note);
1087 GhostRegion::CatchDeletion.connect (*this, ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1093 /** Begin tracking note state for successive calls to add_event
1096 MidiRegionView::begin_write()
1098 assert(!_active_notes);
1099 _active_notes = new CanvasNote*[128];
1100 for (unsigned i=0; i < 128; ++i) {
1101 _active_notes[i] = 0;
1106 /** Destroy note state for add_event
1109 MidiRegionView::end_write()
1111 delete[] _active_notes;
1113 _marked_for_selection.clear();
1114 _marked_for_velocity.clear();
1118 /** Resolve an active MIDI note (while recording).
1121 MidiRegionView::resolve_note(uint8_t note, double end_time)
1123 if (midi_view()->note_mode() != Sustained) {
1127 if (_active_notes && _active_notes[note]) {
1128 const nframes64_t end_time_frames = beats_to_frames(end_time);
1129 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1130 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1131 _active_notes[note] = 0;
1136 /** Extend active notes to rightmost edge of region (if length is changed)
1139 MidiRegionView::extend_active_notes()
1141 if (!_active_notes) {
1145 for (unsigned i=0; i < 128; ++i) {
1146 if (_active_notes[i]) {
1147 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1153 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1155 if (no_sound_notes || !trackview.editor().sound_notes()) {
1159 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1162 route_ui->midi_track()->write_immediate_event(
1163 note->on_event().size(), note->on_event().buffer());
1165 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1166 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1167 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1168 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1169 note_length_ms, G_PRIORITY_DEFAULT);
1173 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1175 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1178 route_ui->midi_track()->write_immediate_event(
1179 note->off_event().size(), note->off_event().buffer());
1185 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1187 const nframes64_t note_start_frames = beats_to_frames(note->time());
1189 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1190 (note_start_frames < _region->start());
1192 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1193 (note->note() <= midi_stream_view()->highest_note());
1199 MidiRegionView::update_note (CanvasNote* ev)
1201 boost::shared_ptr<NoteType> note = ev->note();
1203 const nframes64_t note_start_frames = beats_to_frames(note->time());
1204 const nframes64_t note_end_frames = beats_to_frames(note->end_time());
1206 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1207 const double y1 = midi_stream_view()->note_to_y(note->note());
1208 const double note_endpixel =
1209 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1211 ev->property_x1() = x;
1212 ev->property_y1() = y1;
1213 if (note->length() > 0) {
1214 ev->property_x2() = note_endpixel;
1216 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1218 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1220 if (note->length() == 0) {
1221 if (_active_notes) {
1222 assert(note->note() < 128);
1223 // If this note is already active there's a stuck note,
1224 // finish the old note rectangle
1225 if (_active_notes[note->note()]) {
1226 CanvasNote* const old_rect = _active_notes[note->note()];
1227 boost::shared_ptr<NoteType> old_note = old_rect->note();
1228 old_rect->property_x2() = x;
1229 old_rect->property_outline_what() = (guint32) 0xF;
1231 _active_notes[note->note()] = ev;
1233 /* outline all but right edge */
1234 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1236 /* outline all edges */
1237 ev->property_outline_what() = (guint32) 0xF;
1242 MidiRegionView::update_hit (CanvasHit* ev)
1244 boost::shared_ptr<NoteType> note = ev->note();
1246 const nframes64_t note_start_frames = beats_to_frames(note->time());
1247 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1248 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1249 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1254 /** Add a MIDI note to the view (with length).
1256 * If in sustained mode, notes with length 0 will be considered active
1257 * notes, and resolve_note should be called when the corresponding note off
1258 * event arrives, to properly display the note.
1261 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1263 CanvasNoteEvent* event = 0;
1265 assert(note->time() >= 0);
1266 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1268 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1270 if (midi_view()->note_mode() == Sustained) {
1272 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1274 update_note (ev_rect);
1278 MidiGhostRegion* gr;
1280 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1281 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1282 gr->add_note(ev_rect);
1286 } else if (midi_view()->note_mode() == Percussive) {
1288 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1290 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1292 update_hit (ev_diamond);
1301 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1302 note_selected(event, true);
1305 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1306 event->show_velocity();
1308 event->on_channel_selection_change(_last_channel_selection);
1309 _events.push_back(event);
1320 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1321 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1323 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1325 start_delta_command (_("step add"));
1326 delta_add_note (new_note, true, false);
1329 /* potentially extend region to hold new note */
1331 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1332 nframes64_t region_end = _region->position() + _region->length() - 1;
1334 if (end_frame > region_end) {
1335 _region->set_length (end_frame, this);
1342 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1344 assert(program.time >= 0);
1346 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1347 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1349 double height = midi_stream_view()->contents_height();
1351 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1352 new CanvasProgramChange(*this, *group,
1357 _custom_device_mode,
1358 program.time, program.channel, program.value));
1360 // Show unless program change is beyond the region bounds
1361 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1367 _pgm_changes.push_back(pgm_change);
1371 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1373 cerr << "getting patch key at " << time << " for channel " << channel << endl;
1374 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1375 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1377 if (msb_control != 0) {
1378 msb = int(msb_control->get_float(true, time));
1379 cerr << "got msb " << msb;
1382 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1383 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1385 if (lsb_control != 0) {
1386 lsb = lsb_control->get_float(true, time);
1387 cerr << " got lsb " << lsb;
1390 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1391 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1392 float program_number = -1.0;
1393 if (program_control != 0) {
1394 program_number = program_control->get_float(true, time);
1395 cerr << " got program " << program_number << endl;
1398 key.msb = (int) floor(msb + 0.5);
1399 key.lsb = (int) floor(lsb + 0.5);
1400 key.program_number = (int) floor(program_number + 0.5);
1401 assert(key.is_sane());
1406 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1408 // TODO: Get the real event here and alter them at the original times
1409 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1410 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1411 if (msb_control != 0) {
1412 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1415 // TODO: Get the real event here and alter them at the original times
1416 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1417 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1418 if (lsb_control != 0) {
1419 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1422 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1423 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1425 assert(program_control != 0);
1426 program_control->set_float(float(new_patch.program_number), true, old_program.time);
1432 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1434 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1435 alter_program_change(program_change_event, new_patch);
1439 MidiRegionView::previous_program(CanvasProgramChange& program)
1441 MIDI::Name::PatchPrimaryKey key;
1442 get_patch_key_at(program.event_time(), program.channel(), key);
1444 boost::shared_ptr<MIDI::Name::Patch> patch =
1445 MIDI::Name::MidiPatchManager::instance().previous_patch(
1447 _custom_device_mode,
1451 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1453 alter_program_change(program_change_event, patch->patch_primary_key());
1458 MidiRegionView::next_program(CanvasProgramChange& program)
1460 MIDI::Name::PatchPrimaryKey key;
1461 get_patch_key_at(program.event_time(), program.channel(), key);
1463 boost::shared_ptr<MIDI::Name::Patch> patch =
1464 MIDI::Name::MidiPatchManager::instance().next_patch(
1466 _custom_device_mode,
1470 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1472 alter_program_change(program_change_event, patch->patch_primary_key());
1477 MidiRegionView::delete_selection()
1479 if (_selection.empty()) {
1483 start_delta_command (_("delete selection"));
1485 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1486 if ((*i)->selected()) {
1487 _delta_command->remove((*i)->note());
1497 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1499 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1500 if ((*i)->selected() && (*i) != ev) {
1501 (*i)->selected(false);
1502 (*i)->hide_velocity();
1510 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1512 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1515 Selection::iterator tmp = i;
1518 (*i)->selected (false);
1519 _selection.erase (i);
1528 /* don't bother with removing this regionview from the editor selection,
1529 since we're about to add another note, and thus put/keep this
1530 regionview in the editor selection.
1533 if (!ev->selected()) {
1534 add_to_selection (ev);
1539 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1541 uint8_t low_note = 127;
1542 uint8_t high_note = 0;
1543 MidiModel::Notes& notes (_model->notes());
1544 _optimization_iterator = _events.begin();
1546 if (extend && _selection.empty()) {
1552 /* scan existing selection to get note range */
1554 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1555 if ((*i)->note()->note() < low_note) {
1556 low_note = (*i)->note()->note();
1558 if ((*i)->note()->note() > high_note) {
1559 high_note = (*i)->note()->note();
1563 low_note = min (low_note, notenum);
1564 high_note = max (high_note, notenum);
1567 no_sound_notes = true;
1569 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1571 boost::shared_ptr<NoteType> note (*n);
1572 CanvasNoteEvent* cne;
1573 bool select = false;
1575 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1577 if ((note->note() >= low_note && note->note() <= high_note)) {
1580 } else if (note->note() == notenum) {
1586 if ((cne = find_canvas_note (note)) != 0) {
1587 // extend is false because we've taken care of it,
1588 // since it extends by time range, not pitch.
1589 note_selected (cne, add, false);
1593 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1597 no_sound_notes = false;
1601 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1603 MidiModel::Notes& notes (_model->notes());
1604 _optimization_iterator = _events.begin();
1606 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1608 boost::shared_ptr<NoteType> note (*n);
1609 CanvasNoteEvent* cne;
1611 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1612 if ((cne = find_canvas_note (note)) != 0) {
1613 if (cne->selected()) {
1614 note_deselected (cne);
1616 note_selected (cne, true, false);
1624 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1627 clear_selection_except(ev);
1632 if (!ev->selected()) {
1633 add_to_selection (ev);
1637 /* find end of latest note selected, select all between that and the start of "ev" */
1639 Evoral::MusicalTime earliest = DBL_MAX;
1640 Evoral::MusicalTime latest = 0;
1642 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1643 if ((*i)->note()->end_time() > latest) {
1644 latest = (*i)->note()->end_time();
1646 if ((*i)->note()->time() < earliest) {
1647 earliest = (*i)->note()->time();
1651 if (ev->note()->end_time() > latest) {
1652 latest = ev->note()->end_time();
1655 if (ev->note()->time() < earliest) {
1656 earliest = ev->note()->time();
1659 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1661 /* find notes entirely within OR spanning the earliest..latest range */
1663 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1664 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1665 add_to_selection (*i);
1669 /* if events were guaranteed to be time sorted, we could do this.
1670 but as of sept 10th 2009, they no longer are.
1673 if ((*i)->note()->time() > latest) {
1682 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1684 remove_from_selection (ev);
1688 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1698 // TODO: Make this faster by storing the last updated selection rect, and only
1699 // adjusting things that are in the area that appears/disappeared.
1700 // We probably need a tree to be able to find events in O(log(n)) time.
1702 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1704 /* check if any corner of the note is inside the rect
1707 1) this is computing "touched by", not "contained by" the rect.
1708 2) this does not require that events be sorted in time.
1711 const double ix1 = (*i)->x1();
1712 const double ix2 = (*i)->x2();
1713 const double iy1 = (*i)->y1();
1714 const double iy2 = (*i)->y2();
1716 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1717 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1718 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1719 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1722 if (!(*i)->selected()) {
1723 add_to_selection (*i);
1725 } else if ((*i)->selected()) {
1726 // Not inside rectangle
1727 remove_from_selection (*i);
1733 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1735 Selection::iterator i = _selection.find (ev);
1737 if (i != _selection.end()) {
1738 _selection.erase (i);
1741 ev->selected (false);
1742 ev->hide_velocity ();
1744 if (_selection.empty()) {
1745 PublicEditor& editor (trackview.editor());
1746 editor.get_selection().remove (this);
1751 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1753 bool add_mrv_selection = false;
1755 if (_selection.empty()) {
1756 add_mrv_selection = true;
1759 if (_selection.insert (ev).second) {
1760 cerr << "Added CNE to selection, size now " << _selection.size() << endl;
1761 ev->selected (true);
1762 play_midi_note ((ev)->note());
1765 if (add_mrv_selection) {
1766 PublicEditor& editor (trackview.editor());
1767 editor.get_selection().add (this);
1772 MidiRegionView::move_selection(double dx, double dy)
1774 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1775 (*i)->move_event(dx, dy);
1780 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1782 assert (!_selection.empty());
1784 uint8_t lowest_note_in_selection = 127;
1785 uint8_t highest_note_in_selection = 0;
1786 uint8_t highest_note_difference = 0;
1788 // find highest and lowest notes first
1790 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1791 uint8_t pitch = (*i)->note()->note();
1792 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1793 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1797 cerr << "dnote: " << (int) dnote << endl;
1798 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1799 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1800 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1801 << int(highest_note_in_selection) << endl;
1802 cerr << "selection size: " << _selection.size() << endl;
1803 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1806 // Make sure the note pitch does not exceed the MIDI standard range
1807 if (highest_note_in_selection + dnote > 127) {
1808 highest_note_difference = highest_note_in_selection - 127;
1811 start_diff_command(_("move notes"));
1813 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1815 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1818 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1820 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1823 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1829 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1831 uint8_t original_pitch = (*i)->note()->note();
1832 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
1834 // keep notes in standard midi range
1835 clamp_to_0_127(new_pitch);
1837 // keep original pitch if note is dragged outside valid midi range
1838 if ((original_pitch != 0 && new_pitch == 0)
1839 || (original_pitch != 127 && new_pitch == 127)) {
1840 new_pitch = original_pitch;
1843 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
1844 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1846 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1851 // care about notes being moved beyond the upper/lower bounds on the canvas
1852 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
1853 highest_note_in_selection > midi_stream_view()->highest_note()) {
1854 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1859 MidiRegionView::snap_pixel_to_frame(double x)
1861 PublicEditor& editor = trackview.editor();
1862 // x is region relative, convert it to global absolute frames
1863 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1864 editor.snap_to(frame);
1865 return frame - _region->position(); // convert back to region relative
1869 MidiRegionView::snap_frame_to_frame(nframes64_t x)
1871 PublicEditor& editor = trackview.editor();
1872 // x is region relative, convert it to global absolute frames
1873 nframes64_t frame = x + _region->position();
1874 editor.snap_to(frame);
1875 return frame - _region->position(); // convert back to region relative
1879 MidiRegionView::snap_to_pixel(double x)
1881 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
1885 MidiRegionView::get_position_pixels()
1887 nframes64_t region_frame = get_position();
1888 return trackview.editor().frame_to_pixel(region_frame);
1892 MidiRegionView::get_end_position_pixels()
1894 nframes64_t frame = get_position() + get_duration ();
1895 return trackview.editor().frame_to_pixel(frame);
1899 MidiRegionView::beats_to_frames(double beats) const
1901 return _time_converter.to(beats);
1905 MidiRegionView::frames_to_beats(nframes64_t frames) const
1907 return _time_converter.from(frames);
1911 MidiRegionView::begin_resizing (bool /*at_front*/)
1913 _resize_data.clear();
1915 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1916 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1918 // only insert CanvasNotes into the map
1920 NoteResizeData *resize_data = new NoteResizeData();
1921 resize_data->canvas_note = note;
1923 // create a new SimpleRect from the note which will be the resize preview
1924 SimpleRect *resize_rect = new SimpleRect(
1925 *group, note->x1(), note->y1(), note->x2(), note->y2());
1927 // calculate the colors: get the color settings
1928 uint32_t fill_color = UINT_RGBA_CHANGE_A(
1929 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
1932 // make the resize preview notes more transparent and bright
1933 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1935 // calculate color based on note velocity
1936 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
1937 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
1941 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
1942 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
1944 resize_data->resize_rect = resize_rect;
1945 _resize_data.push_back(resize_data);
1951 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
1953 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1954 SimpleRect* resize_rect = (*i)->resize_rect;
1955 CanvasNote* canvas_note = (*i)->canvas_note;
1960 current_x = canvas_note->x1() + delta_x;
1962 // x is in track relative, transform it to region relative
1963 current_x = delta_x - get_position_pixels();
1967 current_x = canvas_note->x2() + delta_x;
1969 // x is in track relative, transform it to region relative
1970 current_x = delta_x - get_end_position_pixels ();
1975 resize_rect->property_x1() = snap_to_pixel(current_x);
1976 resize_rect->property_x2() = canvas_note->x2();
1978 resize_rect->property_x2() = snap_to_pixel(current_x);
1979 resize_rect->property_x1() = canvas_note->x1();
1985 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
1987 start_diff_command(_("resize notes"));
1989 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1990 CanvasNote* canvas_note = (*i)->canvas_note;
1991 SimpleRect* resize_rect = (*i)->resize_rect;
1992 const double region_start = get_position_pixels();
1997 current_x = canvas_note->x1() + delta_x;
1999 // x is in track relative, transform it to region relative
2000 current_x = region_start + delta_x;
2004 current_x = canvas_note->x2() + delta_x;
2006 // x is in track relative, transform it to region relative
2007 current_x = region_start + delta_x;
2011 current_x = snap_pixel_to_frame (current_x);
2012 current_x = frames_to_beats (current_x);
2014 if (at_front && current_x < canvas_note->note()->end_time()) {
2015 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2017 double len = canvas_note->note()->time() - current_x;
2018 len += canvas_note->note()->length();
2021 /* XXX convert to beats */
2022 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2027 double len = current_x - canvas_note->note()->time();
2030 /* XXX convert to beats */
2031 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2039 _resize_data.clear();
2044 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2046 uint8_t new_velocity;
2049 new_velocity = event->note()->velocity() + velocity;
2050 clamp_to_0_127(new_velocity);
2052 new_velocity = velocity;
2055 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2059 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2064 new_note = event->note()->note() + note;
2069 clamp_to_0_127 (new_note);
2070 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2074 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2076 bool change_start = false;
2077 bool change_length = false;
2078 Evoral::MusicalTime new_start;
2079 Evoral::MusicalTime new_length;
2081 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2083 front_delta: if positive - move the start of the note later in time (shortening it)
2084 if negative - move the start of the note earlier in time (lengthening it)
2086 end_delta: if positive - move the end of the note later in time (lengthening it)
2087 if negative - move the end of the note earlier in time (shortening it)
2091 if (front_delta < 0) {
2093 if (event->note()->time() < -front_delta) {
2096 new_start = event->note()->time() + front_delta; // moves earlier
2099 /* start moved toward zero, so move the end point out to where it used to be.
2100 Note that front_delta is negative, so this increases the length.
2103 new_length = event->note()->length() - front_delta;
2104 change_start = true;
2105 change_length = true;
2109 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2111 if (new_pos < event->note()->end_time()) {
2112 new_start = event->note()->time() + front_delta;
2113 /* start moved toward the end, so move the end point back to where it used to be */
2114 new_length = event->note()->length() - front_delta;
2115 change_start = true;
2116 change_length = true;
2123 bool can_change = true;
2124 if (end_delta < 0) {
2125 if (event->note()->length() < -end_delta) {
2131 new_length = event->note()->length() + end_delta;
2132 change_length = true;
2137 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2140 if (change_length) {
2141 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2146 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2148 Evoral::MusicalTime new_time;
2152 if (event->note()->time() < -delta) {
2155 new_time = event->note()->time() + delta;
2158 new_time = event->note()->time() + delta;
2164 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2168 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2172 if (_selection.empty()) {
2187 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2188 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2194 start_diff_command(_("change velocities"));
2196 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2197 Selection::iterator next = i;
2199 change_note_velocity (*i, delta, true);
2208 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2210 if (_selection.empty()) {
2227 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2229 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2233 if ((int8_t) (*i)->note()->note() + delta > 127) {
2240 start_diff_command (_("transpose"));
2242 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2243 Selection::iterator next = i;
2245 change_note_note (*i, delta, true);
2253 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2255 Evoral::MusicalTime delta;
2260 /* grab the current grid distance */
2262 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2264 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2265 cerr << "Grid type not available as beats - TO BE FIXED\n";
2274 start_diff_command (_("change note lengths"));
2276 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2277 Selection::iterator next = i;
2280 /* note the negation of the delta for start */
2282 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2291 MidiRegionView::nudge_notes (bool forward)
2293 if (_selection.empty()) {
2297 /* pick a note as the point along the timeline to get the nudge distance.
2298 its not necessarily the earliest note, so we may want to pull the notes out
2299 into a vector and sort before using the first one.
2302 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2304 nframes64_t distance;
2306 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2308 /* grid is off - use nudge distance */
2310 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2316 nframes64_t next_pos = ref_point;
2319 /* XXX need check on max_frames, but that needs max_frames64 or something */
2322 if (next_pos == 0) {
2328 cerr << "ref point was " << ref_point << " next was " << next_pos;
2329 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2330 distance = ref_point - next_pos;
2331 cerr << " final is " << next_pos << " distance = " << distance << endl;
2334 if (distance == 0) {
2338 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2344 start_diff_command (_("nudge"));
2346 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2347 Selection::iterator next = i;
2349 change_note_time (*i, delta, true);
2357 MidiRegionView::change_channel(uint8_t channel)
2359 start_diff_command(_("change channel"));
2360 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2361 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2368 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2370 if (_mouse_state == SelectTouchDragging) {
2371 note_selected(ev, true);
2375 snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
2376 // This causes an infinite loop on note add sometimes
2377 //PublicEditor& editor (trackview.editor());
2378 //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
2379 //editor.show_verbose_canvas_cursor_with (buf);
2383 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2385 PublicEditor& editor (trackview.editor());
2386 editor.hide_verbose_canvas_cursor ();
2391 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2393 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2395 display_model(msrc->model());
2399 MidiRegionView::set_frame_color()
2402 if (_selected && should_show_selection) {
2403 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2405 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2411 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2415 case FilterChannels:
2416 _force_channel = -1;
2419 _force_channel = mask;
2420 mask = 0xFFFF; // Show all notes as active (below)
2423 // Update notes for selection
2424 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2425 (*i)->on_channel_selection_change(mask);
2428 _last_channel_selection = mask;
2432 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2434 _model_name = model;
2435 _custom_device_mode = custom_device_mode;
2440 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2442 if (_selection.empty()) {
2446 PublicEditor& editor (trackview.editor());
2451 editor.get_cut_buffer().add (selection_as_cut_buffer());
2457 start_delta_command();
2459 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2464 delta_remove_note (*i);
2475 MidiRegionView::selection_as_cut_buffer () const
2479 cerr << "Convert selection of " << _selection.size() << " into a cut buffer\n";
2481 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2482 NoteType* n = (*i)->note().get();
2483 cerr << "CNE's note is " << n << endl;
2484 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2487 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2494 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2500 start_delta_command (_("paste"));
2502 Evoral::MusicalTime beat_delta;
2503 Evoral::MusicalTime paste_pos_beats;
2504 Evoral::MusicalTime duration;
2505 Evoral::MusicalTime end_point;
2507 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2508 paste_pos_beats = frames_to_beats (pos - _region->position());
2509 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2510 paste_pos_beats = 0;
2512 _selection.clear ();
2514 for (int n = 0; n < (int) times; ++n) {
2516 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2518 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2519 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2521 /* make all newly added notes selected */
2523 delta_add_note (copied_note, true);
2524 end_point = copied_note->end_time();
2527 paste_pos_beats += duration;
2530 /* if we pasted past the current end of the region, extend the region */
2532 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2533 nframes64_t region_end = _region->position() + _region->length() - 1;
2535 if (end_frame > region_end) {
2537 trackview.session()->begin_reversible_command (_("paste"));
2539 _region->clear_history ();
2540 _region->set_length (end_frame, this);
2541 trackview.session()->add_command (new StatefulDiffCommand (_region));
2547 struct EventNoteTimeEarlyFirstComparator {
2548 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2549 return a->note()->time() < b->note()->time();
2554 MidiRegionView::time_sort_events ()
2556 if (!_sort_needed) {
2560 EventNoteTimeEarlyFirstComparator cmp;
2563 _sort_needed = false;
2567 MidiRegionView::goto_next_note ()
2569 // nframes64_t pos = -1;
2570 bool use_next = false;
2572 if (_events.back()->selected()) {
2576 time_sort_events ();
2578 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2579 if ((*i)->selected()) {
2582 } else if (use_next) {
2584 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2589 /* use the first one */
2591 unique_select (_events.front());
2596 MidiRegionView::goto_previous_note ()
2598 // nframes64_t pos = -1;
2599 bool use_next = false;
2601 if (_events.front()->selected()) {
2605 time_sort_events ();
2607 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2608 if ((*i)->selected()) {
2611 } else if (use_next) {
2613 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2618 /* use the last one */
2620 unique_select (*(_events.rbegin()));
2624 MidiRegionView::selection_as_notelist (Notes& selected)
2626 time_sort_events ();
2628 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2629 if ((*i)->selected()) {
2630 selected.insert ((*i)->note());