Zoom session when the mouse pointer is moved up and down during a playhead drag.
[ardour2.git] / gtk2_ardour / midi_time_axis.cc
blob28401fc6d52036fddee12cec2df98afee2317879
1 /*
2 Copyright (C) 2000 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 #include <cstdlib>
20 #include <cmath>
22 #include <algorithm>
23 #include <string>
24 #include <vector>
26 #include <sigc++/bind.h>
28 #include "pbd/error.h"
29 #include "pbd/stl_delete.h"
30 #include "pbd/whitespace.h"
31 #include "pbd/basename.h"
32 #include "pbd/enumwriter.h"
33 #include "pbd/memento_command.h"
34 #include "pbd/stateful_diff_command.h"
36 #include "gtkmm2ext/gtk_ui.h"
37 #include "gtkmm2ext/selector.h"
38 #include "gtkmm2ext/bindable_button.h"
39 #include "gtkmm2ext/utils.h"
41 #include "ardour/file_source.h"
42 #include "ardour/midi_playlist.h"
43 #include "ardour/midi_diskstream.h"
44 #include "ardour/midi_patch_manager.h"
45 #include "ardour/midi_source.h"
46 #include "ardour/processor.h"
47 #include "ardour/ladspa_plugin.h"
48 #include "ardour/location.h"
49 #include "ardour/playlist.h"
50 #include "ardour/region_factory.h"
51 #include "ardour/session.h"
52 #include "ardour/session_playlist.h"
53 #include "ardour/tempo.h"
54 #include "ardour/utils.h"
55 #include "ardour/operations.h"
57 #include "midi++/names.h"
59 #include "add_midi_cc_track_dialog.h"
60 #include "ardour_ui.h"
61 #include "automation_line.h"
62 #include "automation_time_axis.h"
63 #include "canvas-note-event.h"
64 #include "canvas_impl.h"
65 #include "crossfade_view.h"
66 #include "editor.h"
67 #include "enums.h"
68 #include "ghostregion.h"
69 #include "gui_thread.h"
70 #include "keyboard.h"
71 #include "midi_scroomer.h"
72 #include "midi_streamview.h"
73 #include "midi_region_view.h"
74 #include "midi_time_axis.h"
75 #include "piano_roll_header.h"
76 #include "playlist_selector.h"
77 #include "plugin_selector.h"
78 #include "plugin_ui.h"
79 #include "point_selection.h"
80 #include "prompter.h"
81 #include "region_view.h"
82 #include "rgb_macros.h"
83 #include "selection.h"
84 #include "step_editor.h"
85 #include "simplerect.h"
86 #include "utils.h"
88 #include "ardour/midi_track.h"
90 #include "i18n.h"
92 using namespace ARDOUR;
93 using namespace PBD;
94 using namespace Gtk;
95 using namespace Gtkmm2ext;
96 using namespace Editing;
98 // Minimum height at which a control is displayed
99 static const uint32_t MIDI_CONTROLS_BOX_MIN_HEIGHT = 162;
100 static const uint32_t KEYBOARD_MIN_HEIGHT = 140;
102 MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
103 boost::shared_ptr<Route> rt, Canvas& canvas)
104 : AxisView(sess) // virtually inherited
105 , RouteTimeAxisView(ed, sess, rt, canvas)
106 , _ignore_signals(false)
107 , _range_scroomer(0)
108 , _piano_roll_header(0)
109 , _note_mode(Sustained)
110 , _note_mode_item(0)
111 , _percussion_mode_item(0)
112 , _color_mode(MeterColors)
113 , _meter_color_mode_item(0)
114 , _channel_color_mode_item(0)
115 , _track_color_mode_item(0)
116 , _step_edit_item (0)
117 , _midi_thru_item (0)
118 , controller_menu (0)
119 , _step_editor (0)
121 subplugin_menu.set_name ("ArdourContextMenu");
123 _view = new MidiStreamView (*this);
125 ignore_toggle = false;
127 mute_button->set_active (false);
128 solo_button->set_active (false);
130 if (is_midi_track()) {
131 controls_ebox.set_name ("MidiTimeAxisViewControlsBaseUnselected");
132 _note_mode = midi_track()->note_mode();
133 } else { // MIDI bus (which doesn't exist yet..)
134 controls_ebox.set_name ("MidiBusControlsBaseUnselected");
137 /* map current state of the route */
139 processors_changed (RouteProcessorChange ());
141 ensure_xml_node ();
143 set_state (*xml_node, Stateful::loading_state_version);
145 _route->processors_changed.connect (*this, invalidator (*this), ui_bind (&MidiTimeAxisView::processors_changed, this, _1), gui_context());
147 if (is_track()) {
148 _piano_roll_header = new PianoRollHeader(*midi_view());
150 _piano_roll_header->AddNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection));
151 _piano_roll_header->ExtendNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection));
152 _piano_roll_header->ToggleNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection));
154 _range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment);
156 /* Suspend updates of the StreamView during scroomer drags to speed things up */
157 _range_scroomer->DragStarting.connect (sigc::mem_fun (*midi_view(), &MidiStreamView::suspend_updates));
158 _range_scroomer->DragFinishing.connect (sigc::mem_fun (*midi_view(), &MidiStreamView::resume_updates));
160 controls_hbox.pack_start(*_range_scroomer);
161 controls_hbox.pack_start(*_piano_roll_header);
163 controls_ebox.set_name ("MidiTrackControlsBaseUnselected");
164 controls_base_selected_name = "MidiTrackControlsBaseSelected";
165 controls_base_unselected_name = "MidiTrackControlsBaseUnselected";
167 midi_view()->NoteRangeChanged.connect (sigc::mem_fun(*this, &MidiTimeAxisView::update_range));
169 /* ask for notifications of any new RegionViews */
170 _view->RegionViewAdded.connect (sigc::mem_fun(*this, &MidiTimeAxisView::region_view_added));
172 if (!_editor.have_idled()) {
173 /* first idle will do what we need */
174 } else {
175 first_idle ();
179 HBox* midi_controls_hbox = manage(new HBox());
181 MIDI::Name::MidiPatchManager& patch_manager = MIDI::Name::MidiPatchManager::instance();
183 MIDI::Name::MasterDeviceNames::Models::const_iterator m = patch_manager.all_models().begin();
184 for (; m != patch_manager.all_models().end(); ++m) {
185 _model_selector.append_text(m->c_str());
188 _model_selector.signal_changed().connect(sigc::mem_fun(*this, &MidiTimeAxisView::model_changed));
190 _custom_device_mode_selector.signal_changed().connect(
191 sigc::mem_fun(*this, &MidiTimeAxisView::custom_device_mode_changed));
193 // TODO: persist the choice
194 // this initializes the comboboxes and sends out the signal
195 _model_selector.set_active(0);
197 midi_controls_hbox->pack_start(_channel_selector, true, false);
198 if (!patch_manager.all_models().empty()) {
199 _midi_controls_box.pack_start(_model_selector, true, false);
200 _midi_controls_box.pack_start(_custom_device_mode_selector, true, false);
203 _midi_controls_box.pack_start(*midi_controls_hbox, true, true);
205 controls_vbox.pack_start(_midi_controls_box, false, false);
207 // restore channel selector settings
208 _channel_selector.set_channel_mode(midi_track()->get_channel_mode(), midi_track()->get_channel_mask());
209 _channel_selector.mode_changed.connect(
210 sigc::mem_fun(*midi_track(), &MidiTrack::set_channel_mode));
211 _channel_selector.mode_changed.connect(
212 sigc::mem_fun(*this, &MidiTimeAxisView::set_channel_mode));
214 XMLProperty *prop;
215 if ((prop = xml_node->property ("color-mode")) != 0) {
216 _color_mode = ColorMode (string_2_enum(prop->value(), _color_mode));
217 if (_color_mode == ChannelColors) {
218 _channel_selector.set_channel_colors(CanvasNoteEvent::midi_channel_colors);
222 if ((prop = xml_node->property ("note-mode")) != 0) {
223 _note_mode = NoteMode (string_2_enum(prop->value(), _note_mode));
224 if (_percussion_mode_item) {
225 _percussion_mode_item->set_active (_note_mode == Percussive);
229 set_color_mode (_color_mode, true, false);
232 void
233 MidiTimeAxisView::first_idle ()
235 if (is_track ()) {
236 _view->attach ();
240 MidiTimeAxisView::~MidiTimeAxisView ()
242 delete _piano_roll_header;
243 _piano_roll_header = 0;
245 delete _range_scroomer;
246 _range_scroomer = 0;
248 delete controller_menu;
249 delete _step_editor;
252 void
253 MidiTimeAxisView::enter_internal_edit_mode ()
255 if (midi_view()) {
256 midi_view()->enter_internal_edit_mode ();
260 void
261 MidiTimeAxisView::leave_internal_edit_mode ()
263 if (midi_view()) {
264 midi_view()->leave_internal_edit_mode ();
268 void
269 MidiTimeAxisView::check_step_edit ()
271 ensure_step_editor ();
272 _step_editor->check_step_edit ();
275 void
276 MidiTimeAxisView::model_changed()
278 std::list<std::string> device_modes = MIDI::Name::MidiPatchManager::instance()
279 .custom_device_mode_names_by_model(_model_selector.get_active_text());
281 _custom_device_mode_selector.clear_items();
283 for (std::list<std::string>::const_iterator i = device_modes.begin();
284 i != device_modes.end(); ++i) {
285 cerr << "found custom device mode " << *i << " thread_id: " << pthread_self() << endl;
286 _custom_device_mode_selector.append_text(*i);
289 _custom_device_mode_selector.set_active(0);
292 void MidiTimeAxisView::custom_device_mode_changed()
294 _midi_patch_settings_changed.emit(_model_selector.get_active_text(),
295 _custom_device_mode_selector.get_active_text());
298 MidiStreamView*
299 MidiTimeAxisView::midi_view()
301 return dynamic_cast<MidiStreamView*>(_view);
304 guint32
305 MidiTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
307 ensure_xml_node ();
308 xml_node->add_property ("shown-editor", "yes");
310 guint32 ret = TimeAxisView::show_at (y, nth, parent);
311 return ret;
314 void
315 MidiTimeAxisView::hide ()
317 ensure_xml_node ();
318 xml_node->add_property ("shown-editor", "no");
320 TimeAxisView::hide ();
323 void
324 MidiTimeAxisView::set_height (uint32_t h)
326 RouteTimeAxisView::set_height (h);
328 if (height >= MIDI_CONTROLS_BOX_MIN_HEIGHT) {
329 _midi_controls_box.show_all ();
330 } else {
331 _midi_controls_box.hide();
334 if (height >= KEYBOARD_MIN_HEIGHT) {
335 if (is_track() && _range_scroomer)
336 _range_scroomer->show();
337 if (is_track() && _piano_roll_header)
338 _piano_roll_header->show();
339 } else {
340 if (is_track() && _range_scroomer)
341 _range_scroomer->hide();
342 if (is_track() && _piano_roll_header)
343 _piano_roll_header->hide();
347 void
348 MidiTimeAxisView::append_extra_display_menu_items ()
350 using namespace Menu_Helpers;
352 MenuList& items = display_menu->items();
354 // Note range
355 Menu *range_menu = manage(new Menu);
356 MenuList& range_items = range_menu->items();
357 range_menu->set_name ("ArdourContextMenu");
359 range_items.push_back (MenuElem (_("Show Full Range"), sigc::bind (
360 sigc::mem_fun(*this, &MidiTimeAxisView::set_note_range),
361 MidiStreamView::FullRange)));
363 range_items.push_back (MenuElem (_("Fit Contents"), sigc::bind (
364 sigc::mem_fun(*this, &MidiTimeAxisView::set_note_range),
365 MidiStreamView::ContentsRange)));
367 items.push_back (MenuElem (_("Note Range"), *range_menu));
368 items.push_back (MenuElem (_("Note Mode"), *build_note_mode_menu()));
370 items.push_back (CheckMenuElem (_("MIDI Thru"), sigc::mem_fun(*this, &MidiTimeAxisView::toggle_midi_thru)));
371 _midi_thru_item = dynamic_cast<CheckMenuItem*>(&items.back());
373 items.push_back (SeparatorElem ());
376 void
377 MidiTimeAxisView::toggle_midi_thru ()
379 if (!_midi_thru_item) {
380 return;
383 bool view_yn = _midi_thru_item->get_active();
384 if (view_yn != midi_track()->midi_thru()) {
385 midi_track()->set_midi_thru (view_yn);
389 void
390 MidiTimeAxisView::build_automation_action_menu (bool for_selection)
392 using namespace Menu_Helpers;
394 /* If we have a controller menu, we need to detach it before
395 RouteTimeAxis::build_automation_action_menu destroys the
396 menu it is attached to. Otherwise GTK destroys
397 controller_menu's gobj, meaning that it can't be reattached
398 below. See bug #3134.
401 if (controller_menu) {
402 detach_menu (*controller_menu);
405 _channel_command_menu_map.clear ();
406 RouteTimeAxisView::build_automation_action_menu (for_selection);
408 MenuList& automation_items = automation_action_menu->items();
410 uint16_t selected_channels = _channel_selector.get_selected_channels();
412 if (selected_channels != 0) {
414 automation_items.push_back (SeparatorElem());
416 /* these 2 MIDI "command" types are semantically more like automation than note data,
417 but they are not MIDI controllers. We give them special status in this menu, since
418 they will not show up in the controller list and anyone who actually knows
419 something about MIDI (!) would not expect to find them there.
422 add_channel_command_menu_item (automation_items, _("Bender"), MidiPitchBenderAutomation, 0);
423 automation_items.back().set_sensitive (!for_selection || _editor.get_selection().tracks.size() == 1);
424 add_channel_command_menu_item (automation_items, _("Pressure"), MidiChannelPressureAutomation, 0);
425 automation_items.back().set_sensitive (!for_selection || _editor.get_selection().tracks.size() == 1);
427 /* now all MIDI controllers. Always offer the possibility that we will rebuild the controllers menu
428 since it might need to be updated after a channel mode change or other change. Also detach it
429 first in case it has been used anywhere else.
432 build_controller_menu ();
434 automation_items.push_back (SeparatorElem());
435 automation_items.push_back (MenuElem (_("Controllers"), *controller_menu));
436 automation_items.back().set_sensitive (!for_selection || _editor.get_selection().tracks.size() == 1);
437 } else {
438 automation_items.push_back (MenuElem (string_compose ("<i>%1</i>", _("No MIDI Channels selected"))));
439 dynamic_cast<Label*> (automation_items.back().get_child())->set_use_markup (true);
444 void
445 MidiTimeAxisView::change_all_channel_tracks_visibility (bool yn, Evoral::Parameter param)
447 uint16_t selected_channels = _channel_selector.get_selected_channels();
449 for (uint8_t chn = 0; chn < 16; chn++) {
450 if (selected_channels & (0x0001 << chn)) {
452 Evoral::Parameter fully_qualified_param (param.type(), chn, param.id());
453 Gtk::CheckMenuItem* menu = automation_child_menu_item (fully_qualified_param);
455 if (menu) {
456 menu->set_active (yn);
462 void
463 MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items, const string& label, AutomationType auto_type, uint8_t cmd)
465 using namespace Menu_Helpers;
467 /* count the number of selected channels because we will build a different menu structure if there is more than 1 selected.
470 uint16_t selected_channels = _channel_selector.get_selected_channels();
471 int chn_cnt = 0;
473 for (uint8_t chn = 0; chn < 16; chn++) {
474 if (selected_channels & (0x0001 << chn)) {
475 if (++chn_cnt > 1) {
476 break;
481 if (chn_cnt > 1) {
483 /* multiple channels - create a submenu, with 1 item per channel */
485 Menu* chn_menu = manage (new Menu);
486 MenuList& chn_items (chn_menu->items());
487 Evoral::Parameter param_without_channel (auto_type, 0, cmd);
489 /* add a couple of items to hide/show all of them */
491 chn_items.push_back (MenuElem (_("Hide all channels"),
492 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
493 false, param_without_channel)));
494 chn_items.push_back (MenuElem (_("Show all channels"),
495 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
496 true, param_without_channel)));
498 for (uint8_t chn = 0; chn < 16; chn++) {
499 if (selected_channels & (0x0001 << chn)) {
501 /* for each selected channel, add a menu item for this controller */
503 Evoral::Parameter fully_qualified_param (auto_type, chn, cmd);
504 chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1),
505 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
506 fully_qualified_param)));
508 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
509 bool visible = false;
511 if (track) {
512 if (track->marked_for_display()) {
513 visible = true;
517 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&chn_items.back());
518 _channel_command_menu_map[fully_qualified_param] = cmi;
519 cmi->set_active (visible);
523 /* now create an item in the parent menu that has the per-channel list as a submenu */
525 items.push_back (MenuElem (label, *chn_menu));
527 } else {
529 /* just one channel - create a single menu item for this command+channel combination*/
531 for (uint8_t chn = 0; chn < 16; chn++) {
532 if (selected_channels & (0x0001 << chn)) {
534 Evoral::Parameter fully_qualified_param (auto_type, chn, cmd);
535 items.push_back (CheckMenuElem (label,
536 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
537 fully_qualified_param)));
539 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
540 bool visible = false;
542 if (track) {
543 if (track->marked_for_display()) {
544 visible = true;
548 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&items.back());
549 _channel_command_menu_map[fully_qualified_param] = cmi;
550 cmi->set_active (visible);
552 /* one channel only */
553 break;
559 void
560 MidiTimeAxisView::build_controller_menu ()
562 using namespace Menu_Helpers;
564 if (controller_menu) {
565 /* it exists and has not been invalidated by a channel mode change, so just return it */
566 return;
569 controller_menu = new Menu; // explicitly managed by us
570 MenuList& items (controller_menu->items());
572 /* create several "top level" menu items for sets of controllers (16 at a time), and populate each one with a submenu
573 for each controller+channel combination covering the currently selected channels for this track
576 uint16_t selected_channels = _channel_selector.get_selected_channels();
578 /* count the number of selected channels because we will build a different menu structure if there is more than 1 selected.
581 int chn_cnt = 0;
583 for (uint8_t chn = 0; chn < 16; chn++) {
584 if (selected_channels & (0x0001 << chn)) {
585 if (++chn_cnt > 1) {
586 break;
591 /* loop over all 127 MIDI controllers, in groups of 16; except don't offer
592 bank select controllers, as they are handled by the `patch' code */
594 for (int i = 0; i < 127; i += 16) {
596 Menu* ctl_menu = manage (new Menu);
597 MenuList& ctl_items (ctl_menu->items());
600 /* for each controller, consider whether to create a submenu or a single item */
602 for (int ctl = i; ctl < i+16; ++ctl) {
604 if (ctl == MIDI_CTL_MSB_BANK || ctl == MIDI_CTL_LSB_BANK) {
605 continue;
608 if (chn_cnt > 1) {
610 /* multiple channels - create a submenu, with 1 item per channel */
612 Menu* chn_menu = manage (new Menu);
613 MenuList& chn_items (chn_menu->items());
615 /* add a couple of items to hide/show this controller on all channels */
617 Evoral::Parameter param_without_channel (MidiCCAutomation, 0, ctl);
618 chn_items.push_back (MenuElem (_("Hide all channels"),
619 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
620 false, param_without_channel)));
621 chn_items.push_back (MenuElem (_("Show all channels"),
622 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
623 true, param_without_channel)));
625 for (uint8_t chn = 0; chn < 16; chn++) {
626 if (selected_channels & (0x0001 << chn)) {
628 /* for each selected channel, add a menu item for this controller */
630 Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
631 chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1),
632 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
633 fully_qualified_param)));
635 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
636 bool visible = false;
638 if (track) {
639 if (track->marked_for_display()) {
640 visible = true;
644 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&chn_items.back());
645 _controller_menu_map[fully_qualified_param] = cmi;
646 cmi->set_active (visible);
650 /* add the per-channel menu to the list of controllers, with the name of the controller */
651 ctl_items.push_back (MenuElem (string_compose ("<b>%1</b>: %2", ctl, midi_name (ctl)), *chn_menu));
652 dynamic_cast<Label*> (ctl_items.back().get_child())->set_use_markup (true);
654 } else {
656 /* just one channel - create a single menu item for this ctl+channel combination*/
658 for (uint8_t chn = 0; chn < 16; chn++) {
659 if (selected_channels & (0x0001 << chn)) {
661 Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
662 ctl_items.push_back (
663 CheckMenuElem (
664 string_compose ("<b>%1</b>: %2 [%3]", ctl, midi_name (ctl), int (chn)),
665 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
666 fully_qualified_param)
669 dynamic_cast<Label*> (ctl_items.back().get_child())->set_use_markup (true);
671 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
672 bool visible = false;
674 if (track) {
675 if (track->marked_for_display()) {
676 visible = true;
680 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&ctl_items.back());
681 _controller_menu_map[fully_qualified_param] = cmi;
682 cmi->set_active (visible);
684 /* one channel only */
685 break;
691 /* add the menu for this block of controllers to the overall controller menu */
693 items.push_back (MenuElem (string_compose (_("Controllers %1-%2"), i, i+15), *ctl_menu));
697 Gtk::Menu*
698 MidiTimeAxisView::build_note_mode_menu()
700 using namespace Menu_Helpers;
702 Menu* mode_menu = manage (new Menu);
703 MenuList& items = mode_menu->items();
704 mode_menu->set_name ("ArdourContextMenu");
706 RadioMenuItem::Group mode_group;
707 items.push_back (RadioMenuElem (mode_group, _("Sustained"),
708 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_note_mode), Sustained)));
709 _note_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
710 _note_mode_item->set_active(_note_mode == Sustained);
712 items.push_back (RadioMenuElem (mode_group, _("Percussive"),
713 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_note_mode), Percussive)));
714 _percussion_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
715 _percussion_mode_item->set_active(_note_mode == Percussive);
717 return mode_menu;
720 Gtk::Menu*
721 MidiTimeAxisView::build_color_mode_menu()
723 using namespace Menu_Helpers;
725 Menu* mode_menu = manage (new Menu);
726 MenuList& items = mode_menu->items();
727 mode_menu->set_name ("ArdourContextMenu");
729 RadioMenuItem::Group mode_group;
730 items.push_back (RadioMenuElem (mode_group, _("Meter Colors"),
731 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode),
732 MeterColors, false, true)));
733 _meter_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
734 _meter_color_mode_item->set_active(_color_mode == MeterColors);
736 items.push_back (RadioMenuElem (mode_group, _("Channel Colors"),
737 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode),
738 ChannelColors, false, true)));
739 _channel_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
740 _channel_color_mode_item->set_active(_color_mode == ChannelColors);
742 items.push_back (RadioMenuElem (mode_group, _("Track Color"),
743 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode),
744 TrackColor, false, true)));
745 _channel_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
746 _channel_color_mode_item->set_active(_color_mode == TrackColor);
748 return mode_menu;
751 void
752 MidiTimeAxisView::set_note_mode(NoteMode mode)
754 if (_note_mode != mode || midi_track()->note_mode() != mode) {
755 _note_mode = mode;
756 midi_track()->set_note_mode(mode);
757 xml_node->add_property ("note-mode", enum_2_string(_note_mode));
758 _view->redisplay_track();
762 void
763 MidiTimeAxisView::set_color_mode (ColorMode mode, bool force, bool redisplay)
765 if (_color_mode == mode && !force) {
766 return;
769 if (mode == ChannelColors) {
770 _channel_selector.set_channel_colors(CanvasNoteEvent::midi_channel_colors);
771 } else {
772 _channel_selector.set_default_channel_color();
775 _color_mode = mode;
776 xml_node->add_property ("color-mode", enum_2_string(_color_mode));
777 if (redisplay) {
778 _view->redisplay_track();
782 void
783 MidiTimeAxisView::set_note_range(MidiStreamView::VisibleNoteRange range)
785 if (!_ignore_signals)
786 midi_view()->set_note_range(range);
790 void
791 MidiTimeAxisView::update_range()
793 MidiGhostRegion* mgr;
795 for(list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
796 if ((mgr = dynamic_cast<MidiGhostRegion*>(*i)) != 0) {
797 mgr->update_range();
802 void
803 MidiTimeAxisView::show_all_automation (bool apply_to_selection)
805 if (apply_to_selection) {
806 _editor.get_selection().tracks.foreach_midi_time_axis (boost::bind (&MidiTimeAxisView::show_all_automation, _1, false));
807 } else {
808 if (midi_track()) {
809 const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
811 for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
812 create_automation_child(*i, true);
816 RouteTimeAxisView::show_all_automation ();
820 void
821 MidiTimeAxisView::show_existing_automation (bool apply_to_selection)
823 if (apply_to_selection) {
824 _editor.get_selection().tracks.foreach_midi_time_axis (boost::bind (&MidiTimeAxisView::show_existing_automation, _1, false));
825 } else {
826 if (midi_track()) {
827 const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
829 for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
830 create_automation_child(*i, true);
834 RouteTimeAxisView::show_existing_automation ();
838 /** Create an automation track for the given parameter (pitch bend, channel pressure).
840 void
841 MidiTimeAxisView::create_automation_child (const Evoral::Parameter& param, bool show)
843 if (param.type() == NullAutomation) {
844 cerr << "WARNING: Attempt to create NullAutomation child, ignoring" << endl;
845 return;
848 AutomationTracks::iterator existing = _automation_tracks.find (param);
849 if (existing != _automation_tracks.end()) {
850 return;
853 if (param.type() == GainAutomation) {
854 create_gain_automation_child (param, show);
855 } else {
857 /* These controllers are region "automation", so we do not create
858 * an AutomationList/Line for the track */
860 boost::shared_ptr<AutomationTimeAxisView> track (
861 new AutomationTimeAxisView (
862 _session,
863 _route,
864 boost::shared_ptr<Automatable> (),
865 boost::shared_ptr<AutomationControl> (),
866 param,
867 _editor,
868 *this,
869 true,
870 parent_canvas,
871 _route->describe_parameter(param)
875 if (_view) {
876 _view->foreach_regionview (sigc::mem_fun (*track.get(), &TimeAxisView::add_ghost));
879 add_automation_child (param, track, show);
884 void
885 MidiTimeAxisView::route_active_changed ()
887 RouteUI::route_active_changed ();
889 if (is_track()) {
890 if (_route->active()) {
891 controls_ebox.set_name ("MidiTrackControlsBaseUnselected");
892 controls_base_selected_name = "MidiTrackControlsBaseSelected";
893 controls_base_unselected_name = "MidiTrackControlsBaseUnselected";
894 } else {
895 controls_ebox.set_name ("MidiTrackControlsBaseInactiveUnselected");
896 controls_base_selected_name = "MidiTrackControlsBaseInactiveSelected";
897 controls_base_unselected_name = "MidiTrackControlsBaseInactiveUnselected";
899 } else {
901 throw; // wha?
903 if (_route->active()) {
904 controls_ebox.set_name ("BusControlsBaseUnselected");
905 controls_base_selected_name = "BusControlsBaseSelected";
906 controls_base_unselected_name = "BusControlsBaseUnselected";
907 } else {
908 controls_ebox.set_name ("BusControlsBaseInactiveUnselected");
909 controls_base_selected_name = "BusControlsBaseInactiveSelected";
910 controls_base_unselected_name = "BusControlsBaseInactiveUnselected";
917 void
918 MidiTimeAxisView::add_note_selection (uint8_t note)
920 if (!_editor.internal_editing()) {
921 return;
924 uint16_t chn_mask = _channel_selector.get_selected_channels();
926 if (_view->num_selected_regionviews() == 0) {
927 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask));
928 } else {
929 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask));
933 void
934 MidiTimeAxisView::extend_note_selection (uint8_t note)
936 if (!_editor.internal_editing()) {
937 return;
940 uint16_t chn_mask = _channel_selector.get_selected_channels();
942 if (_view->num_selected_regionviews() == 0) {
943 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask));
944 } else {
945 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask));
949 void
950 MidiTimeAxisView::toggle_note_selection (uint8_t note)
952 if (!_editor.internal_editing()) {
953 return;
956 uint16_t chn_mask = _channel_selector.get_selected_channels();
958 if (_view->num_selected_regionviews() == 0) {
959 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask));
960 } else {
961 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask));
965 void
966 MidiTimeAxisView::add_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
968 dynamic_cast<MidiRegionView*>(rv)->select_matching_notes (note, chn_mask, false, false);
971 void
972 MidiTimeAxisView::extend_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
974 dynamic_cast<MidiRegionView*>(rv)->select_matching_notes (note, chn_mask, true, true);
977 void
978 MidiTimeAxisView::toggle_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
980 dynamic_cast<MidiRegionView*>(rv)->toggle_matching_notes (note, chn_mask);
983 void
984 MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t)
986 /* hide all automation tracks that use the wrong channel(s) and show all those that use
987 the right ones.
990 uint16_t selected_channels = _channel_selector.get_selected_channels();
991 bool changed = false;
993 no_redraw = true;
995 for (uint32_t ctl = 0; ctl < 127; ++ctl) {
997 for (uint32_t chn = 0; chn < 16; ++chn) {
998 Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
999 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
1001 if (!track) {
1002 continue;
1005 if ((selected_channels & (0x0001 << chn)) == 0) {
1006 /* channel not in use. hiding it will trigger RouteTimeAxisView::automation_track_hidden()
1007 which will cause a redraw. We don't want one per channel, so block that with no_redraw.
1009 changed = track->set_visibility (false) || changed;
1010 } else {
1011 changed = track->set_visibility (true) || changed;
1016 no_redraw = false;
1018 /* TODO: Bender, Pressure */
1020 /* invalidate the controller menu, so that we rebuild it next time */
1021 _controller_menu_map.clear ();
1022 delete controller_menu;
1023 controller_menu = 0;
1025 if (changed) {
1026 _route->gui_changed ("track_height", this);
1030 Gtk::CheckMenuItem*
1031 MidiTimeAxisView::automation_child_menu_item (Evoral::Parameter param)
1033 Gtk::CheckMenuItem* m = RouteTimeAxisView::automation_child_menu_item (param);
1034 if (m) {
1035 return m;
1038 ParameterMenuMap::iterator i = _controller_menu_map.find (param);
1039 if (i != _controller_menu_map.end()) {
1040 return i->second;
1043 i = _channel_command_menu_map.find (param);
1044 if (i != _channel_command_menu_map.end()) {
1045 return i->second;
1048 return 0;
1051 boost::shared_ptr<MidiRegion>
1052 MidiTimeAxisView::add_region (framepos_t pos, framecnt_t length, bool commit)
1054 Editor* real_editor = dynamic_cast<Editor*> (&_editor);
1056 real_editor->begin_reversible_command (Operations::create_region);
1057 playlist()->clear_changes ();
1059 real_editor->snap_to (pos, 0);
1061 boost::shared_ptr<Source> src = _session->create_midi_source_for_session (view()->trackview().track().get(),
1062 view()->trackview().track()->name());
1063 PropertyList plist;
1065 plist.add (ARDOUR::Properties::start, 0);
1066 plist.add (ARDOUR::Properties::length, length);
1067 plist.add (ARDOUR::Properties::name, PBD::basename_nosuffix(src->name()));
1069 boost::shared_ptr<Region> region = (RegionFactory::create (src, plist));
1071 playlist()->add_region (region, pos);
1072 _session->add_command (new StatefulDiffCommand (playlist()));
1074 if (commit) {
1075 real_editor->commit_reversible_command ();
1078 return boost::dynamic_pointer_cast<MidiRegion>(region);
1081 void
1082 MidiTimeAxisView::ensure_step_editor ()
1084 if (!_step_editor) {
1085 _step_editor = new StepEditor (_editor, midi_track(), *this);
1089 void
1090 MidiTimeAxisView::start_step_editing ()
1092 ensure_step_editor ();
1093 _step_editor->start_step_editing ();
1096 void
1097 MidiTimeAxisView::stop_step_editing ()
1099 if (_step_editor) {
1100 _step_editor->stop_step_editing ();
1105 /** @return channel (counted from 0) to add an event to, based on the current setting
1106 * of the channel selector.
1108 uint8_t
1109 MidiTimeAxisView::get_channel_for_add () const
1111 uint16_t const chn_mask = _channel_selector.get_selected_channels ();
1112 int chn_cnt = 0;
1113 uint8_t channel = 0;
1115 /* pick the highest selected channel, unless all channels are selected,
1116 which is interpreted to mean channel 1 (zero)
1119 for (uint16_t i = 0; i < 16; ++i) {
1120 if (chn_mask & (1<<i)) {
1121 channel = i;
1122 chn_cnt++;
1126 if (chn_cnt == 16) {
1127 channel = 0;
1130 return channel;