extend step-edited region correctly; scroll to keep newly step-edited notes in view
[ardour2.git] / gtk2_ardour / midi_time_axis.cc
blob4a13004a966362efe7516d3cb330c2a0ef1f8f46
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"
56 #include "midi++/names.h"
58 #include "add_midi_cc_track_dialog.h"
59 #include "ardour_ui.h"
60 #include "automation_line.h"
61 #include "automation_time_axis.h"
62 #include "canvas-note-event.h"
63 #include "canvas_impl.h"
64 #include "crossfade_view.h"
65 #include "editor.h"
66 #include "enums.h"
67 #include "ghostregion.h"
68 #include "gui_thread.h"
69 #include "keyboard.h"
70 #include "midi_scroomer.h"
71 #include "midi_streamview.h"
72 #include "midi_region_view.h"
73 #include "midi_time_axis.h"
74 #include "piano_roll_header.h"
75 #include "playlist_selector.h"
76 #include "plugin_selector.h"
77 #include "plugin_ui.h"
78 #include "point_selection.h"
79 #include "prompter.h"
80 #include "region_view.h"
81 #include "rgb_macros.h"
82 #include "selection.h"
83 #include "simplerect.h"
84 #include "step_entry.h"
85 #include "utils.h"
87 #include "ardour/midi_track.h"
89 #include "i18n.h"
91 using namespace ARDOUR;
92 using namespace PBD;
93 using namespace Gtk;
94 using namespace Gtkmm2ext;
95 using namespace Editing;
97 // Minimum height at which a control is displayed
98 static const uint32_t MIDI_CONTROLS_BOX_MIN_HEIGHT = 162;
99 static const uint32_t KEYBOARD_MIN_HEIGHT = 140;
101 MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
102 boost::shared_ptr<Route> rt, Canvas& canvas)
103 : AxisView(sess) // virtually inherited
104 , RouteTimeAxisView(ed, sess, rt, canvas)
105 , _ignore_signals(false)
106 , _range_scroomer(0)
107 , _piano_roll_header(0)
108 , _note_mode(Sustained)
109 , _note_mode_item(0)
110 , _percussion_mode_item(0)
111 , _color_mode(MeterColors)
112 , _meter_color_mode_item(0)
113 , _channel_color_mode_item(0)
114 , _track_color_mode_item(0)
115 , _step_edit_item (0)
116 , _midi_thru_item (0)
117 , default_channel_menu (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 step_edit_insert_position = 0;
132 if (is_midi_track()) {
133 controls_ebox.set_name ("MidiTimeAxisViewControlsBaseUnselected");
134 _note_mode = midi_track()->note_mode();
135 } else { // MIDI bus (which doesn't exist yet..)
136 controls_ebox.set_name ("MidiBusControlsBaseUnselected");
139 /* map current state of the route */
141 processors_changed (RouteProcessorChange ());
143 ensure_xml_node ();
145 set_state (*xml_node, Stateful::loading_state_version);
147 _route->processors_changed.connect (*this, invalidator (*this), ui_bind (&MidiTimeAxisView::processors_changed, this, _1), gui_context());
149 if (is_track()) {
150 _piano_roll_header = new PianoRollHeader(*midi_view());
152 _piano_roll_header->AddNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection));
153 _piano_roll_header->ExtendNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection));
154 _piano_roll_header->ToggleNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection));
156 _range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment);
158 controls_hbox.pack_start(*_range_scroomer);
159 controls_hbox.pack_start(*_piano_roll_header);
161 controls_ebox.set_name ("MidiTrackControlsBaseUnselected");
162 controls_base_selected_name = "MidiTrackControlsBaseSelected";
163 controls_base_unselected_name = "MidiTrackControlsBaseUnselected";
165 midi_view()->NoteRangeChanged.connect (sigc::mem_fun(*this, &MidiTimeAxisView::update_range));
167 /* ask for notifications of any new RegionViews */
168 _view->RegionViewAdded.connect (sigc::mem_fun(*this, &MidiTimeAxisView::region_view_added));
169 _view->attach ();
171 midi_track()->PlaylistChanged.connect (*this, invalidator (*this),
172 boost::bind (&MidiTimeAxisView::playlist_changed, this),
173 gui_context());
174 playlist_changed ();
178 HBox* midi_controls_hbox = manage(new HBox());
180 MIDI::Name::MidiPatchManager& patch_manager = MIDI::Name::MidiPatchManager::instance();
182 MIDI::Name::MasterDeviceNames::Models::const_iterator m = patch_manager.all_models().begin();
183 for (; m != patch_manager.all_models().end(); ++m) {
184 _model_selector.append_text(m->c_str());
187 _model_selector.signal_changed().connect(sigc::mem_fun(*this, &MidiTimeAxisView::model_changed));
189 _custom_device_mode_selector.signal_changed().connect(
190 sigc::mem_fun(*this, &MidiTimeAxisView::custom_device_mode_changed));
192 // TODO: persist the choice
193 // this initializes the comboboxes and sends out the signal
194 _model_selector.set_active(0);
196 midi_controls_hbox->pack_start(_channel_selector, true, false);
197 if (!patch_manager.all_models().empty()) {
198 _midi_controls_box.pack_start(_model_selector, true, false);
199 _midi_controls_box.pack_start(_custom_device_mode_selector, true, false);
202 _midi_controls_box.pack_start(*midi_controls_hbox, true, true);
204 controls_vbox.pack_start(_midi_controls_box, false, false);
206 // restore channel selector settings
207 _channel_selector.set_channel_mode(midi_track()->get_channel_mode(), midi_track()->get_channel_mask());
208 _channel_selector.mode_changed.connect(
209 sigc::mem_fun(*midi_track(), &MidiTrack::set_channel_mode));
210 _channel_selector.mode_changed.connect(
211 sigc::mem_fun(*this, &MidiTimeAxisView::set_channel_mode));
213 XMLProperty *prop;
214 if ((prop = xml_node->property ("color-mode")) != 0) {
215 _color_mode = ColorMode (string_2_enum(prop->value(), _color_mode));
216 if (_color_mode == ChannelColors) {
217 _channel_selector.set_channel_colors(CanvasNoteEvent::midi_channel_colors);
221 if ((prop = xml_node->property ("note-mode")) != 0) {
222 _note_mode = NoteMode (string_2_enum(prop->value(), _note_mode));
223 if (_percussion_mode_item) {
224 _percussion_mode_item->set_active (_note_mode == Percussive);
229 MidiTimeAxisView::~MidiTimeAxisView ()
231 delete _piano_roll_header;
232 _piano_roll_header = 0;
234 delete _range_scroomer;
235 _range_scroomer = 0;
237 delete controller_menu;
240 void
241 MidiTimeAxisView::playlist_changed ()
243 step_edit_region_connection.disconnect ();
244 midi_track()->playlist()->RegionRemoved.connect (step_edit_region_connection, invalidator (*this),
245 ui_bind (&MidiTimeAxisView::region_removed, this, _1),
246 gui_context());
249 void
250 MidiTimeAxisView::region_removed (boost::weak_ptr<Region> wr)
252 boost::shared_ptr<Region> r (wr.lock());
254 if (!r) {
255 return;
258 if (step_edit_region == r) {
259 step_edit_region.reset();
260 // force a recompute of the insert position
261 step_edit_beat_pos = -1.0;
265 void MidiTimeAxisView::model_changed()
267 std::list<std::string> device_modes = MIDI::Name::MidiPatchManager::instance()
268 .custom_device_mode_names_by_model(_model_selector.get_active_text());
270 _custom_device_mode_selector.clear_items();
272 for (std::list<std::string>::const_iterator i = device_modes.begin();
273 i != device_modes.end(); ++i) {
274 cerr << "found custom device mode " << *i << " thread_id: " << pthread_self() << endl;
275 _custom_device_mode_selector.append_text(*i);
278 _custom_device_mode_selector.set_active(0);
281 void MidiTimeAxisView::custom_device_mode_changed()
283 _midi_patch_settings_changed.emit(_model_selector.get_active_text(),
284 _custom_device_mode_selector.get_active_text());
287 MidiStreamView*
288 MidiTimeAxisView::midi_view()
290 return dynamic_cast<MidiStreamView*>(_view);
293 guint32
294 MidiTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
296 ensure_xml_node ();
297 xml_node->add_property ("shown-editor", "yes");
299 guint32 ret = TimeAxisView::show_at (y, nth, parent);
300 return ret;
303 void
304 MidiTimeAxisView::hide ()
306 ensure_xml_node ();
307 xml_node->add_property ("shown-editor", "no");
309 TimeAxisView::hide ();
312 void
313 MidiTimeAxisView::set_height (uint32_t h)
315 RouteTimeAxisView::set_height (h);
317 if (height >= MIDI_CONTROLS_BOX_MIN_HEIGHT) {
318 _midi_controls_box.show_all ();
319 } else {
320 _midi_controls_box.hide();
323 if (height >= KEYBOARD_MIN_HEIGHT) {
324 if (is_track() && _range_scroomer)
325 _range_scroomer->show();
326 if (is_track() && _piano_roll_header)
327 _piano_roll_header->show();
328 } else {
329 if (is_track() && _range_scroomer)
330 _range_scroomer->hide();
331 if (is_track() && _piano_roll_header)
332 _piano_roll_header->hide();
336 void
337 MidiTimeAxisView::append_extra_display_menu_items ()
339 using namespace Menu_Helpers;
341 MenuList& items = display_menu->items();
343 // Note range
344 Menu *range_menu = manage(new Menu);
345 MenuList& range_items = range_menu->items();
346 range_menu->set_name ("ArdourContextMenu");
348 range_items.push_back (MenuElem (_("Show Full Range"), sigc::bind (
349 sigc::mem_fun(*this, &MidiTimeAxisView::set_note_range),
350 MidiStreamView::FullRange)));
352 range_items.push_back (MenuElem (_("Fit Contents"), sigc::bind (
353 sigc::mem_fun(*this, &MidiTimeAxisView::set_note_range),
354 MidiStreamView::ContentsRange)));
356 items.push_back (MenuElem (_("Note range"), *range_menu));
357 items.push_back (MenuElem (_("Note mode"), *build_note_mode_menu()));
358 items.push_back (MenuElem (_("Default Channel"), *build_def_channel_menu()));
360 items.push_back (CheckMenuElem (_("MIDI Thru"), sigc::mem_fun(*this, &MidiTimeAxisView::toggle_midi_thru)));
361 _midi_thru_item = dynamic_cast<CheckMenuItem*>(&items.back());
364 Gtk::Menu*
365 MidiTimeAxisView::build_def_channel_menu ()
367 using namespace Menu_Helpers;
369 default_channel_menu = manage (new Menu ());
371 uint8_t defchn = midi_track()->default_channel();
372 MenuList& def_channel_items = default_channel_menu->items();
373 RadioMenuItem* item;
374 RadioMenuItem::Group dc_group;
376 for (int i = 0; i < 16; ++i) {
377 char buf[4];
378 snprintf (buf, sizeof (buf), "%d", i+1);
380 def_channel_items.push_back (RadioMenuElem (dc_group, buf,
381 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_default_channel), i)));
382 item = dynamic_cast<RadioMenuItem*>(&def_channel_items.back());
383 item->set_active ((i == defchn));
386 return default_channel_menu;
389 void
390 MidiTimeAxisView::set_default_channel (int chn)
392 midi_track()->set_default_channel (chn);
395 void
396 MidiTimeAxisView::toggle_midi_thru ()
398 if (!_midi_thru_item) {
399 return;
402 bool view_yn = _midi_thru_item->get_active();
403 if (view_yn != midi_track()->midi_thru()) {
404 midi_track()->set_midi_thru (view_yn);
408 void
409 MidiTimeAxisView::build_automation_action_menu ()
411 using namespace Menu_Helpers;
413 /* If we have a controller menu, we need to detach it before
414 RouteTimeAxis::build_automation_action_menu destroys the
415 menu it is attached to. Otherwise GTK destroys
416 controller_menu's gobj, meaning that it can't be reattached
417 below. See bug #3134.
420 if (controller_menu) {
421 detach_menu (*controller_menu);
424 _channel_command_menu_map.clear ();
425 RouteTimeAxisView::build_automation_action_menu ();
427 MenuList& automation_items = automation_action_menu->items();
429 uint16_t selected_channels = _channel_selector.get_selected_channels();
431 if (selected_channels != 0) {
433 automation_items.push_back (SeparatorElem());
435 /* these 3 MIDI "command" types are semantically more like automation than note data,
436 but they are not MIDI controllers. We give them special status in this menu, since
437 they will not show up in the controller list and anyone who actually knows
438 something about MIDI (!) would not expect to find them there.
441 add_channel_command_menu_item (automation_items, _("Program Change"), MidiPgmChangeAutomation, 0);
442 add_channel_command_menu_item (automation_items, _("Bender"), MidiPitchBenderAutomation, 0);
443 add_channel_command_menu_item (automation_items, _("Pressure"), MidiChannelPressureAutomation, 0);
445 /* now all MIDI controllers. Always offer the possibility that we will rebuild the controllers menu
446 since it might need to be updated after a channel mode change or other change. Also detach it
447 first in case it has been used anywhere else.
450 build_controller_menu ();
452 automation_items.push_back (SeparatorElem());
453 automation_items.push_back (MenuElem (_("Controllers"), *controller_menu));
454 } else {
455 automation_items.push_back (MenuElem (string_compose ("<i>%1</i>", _("No MIDI Channels selected"))));
460 void
461 MidiTimeAxisView::change_all_channel_tracks_visibility (bool yn, Evoral::Parameter param)
463 uint16_t selected_channels = _channel_selector.get_selected_channels();
465 for (uint8_t chn = 0; chn < 16; chn++) {
466 if (selected_channels & (0x0001 << chn)) {
468 Evoral::Parameter fully_qualified_param (param.type(), chn, param.id());
469 Gtk::CheckMenuItem* menu = automation_child_menu_item (fully_qualified_param);
471 if (menu) {
472 menu->set_active (yn);
478 void
479 MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items, const string& label, AutomationType auto_type, uint8_t cmd)
481 using namespace Menu_Helpers;
483 /* count the number of selected channels because we will build a different menu structure if there is more than 1 selected.
486 uint16_t selected_channels = _channel_selector.get_selected_channels();
487 int chn_cnt = 0;
489 for (uint8_t chn = 0; chn < 16; chn++) {
490 if (selected_channels & (0x0001 << chn)) {
491 if (++chn_cnt > 1) {
492 break;
497 if (chn_cnt > 1) {
499 /* multiple channels - create a submenu, with 1 item per channel */
501 Menu* chn_menu = manage (new Menu);
502 MenuList& chn_items (chn_menu->items());
503 Evoral::Parameter param_without_channel (auto_type, 0, cmd);
505 /* add a couple of items to hide/show all of them */
507 chn_items.push_back (MenuElem (_("Hide all channels"),
508 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
509 false, param_without_channel)));
510 chn_items.push_back (MenuElem (_("Show all channels"),
511 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
512 true, param_without_channel)));
514 for (uint8_t chn = 0; chn < 16; chn++) {
515 if (selected_channels & (0x0001 << chn)) {
517 /* for each selected channel, add a menu item for this controller */
519 Evoral::Parameter fully_qualified_param (auto_type, chn, cmd);
520 chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1),
521 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
522 fully_qualified_param)));
524 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
525 bool visible = false;
527 if (track) {
528 if (track->marked_for_display()) {
529 visible = true;
533 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&chn_items.back());
534 _channel_command_menu_map[fully_qualified_param] = cmi;
535 cmi->set_active (visible);
539 /* now create an item in the parent menu that has the per-channel list as a submenu */
541 items.push_back (MenuElem (label, *chn_menu));
543 } else {
545 /* just one channel - create a single menu item for this command+channel combination*/
547 for (uint8_t chn = 0; chn < 16; chn++) {
548 if (selected_channels & (0x0001 << chn)) {
550 Evoral::Parameter fully_qualified_param (auto_type, chn, cmd);
551 items.push_back (CheckMenuElem (label,
552 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
553 fully_qualified_param)));
555 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
556 bool visible = false;
558 if (track) {
559 if (track->marked_for_display()) {
560 visible = true;
564 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&items.back());
565 _channel_command_menu_map[fully_qualified_param] = cmi;
566 cmi->set_active (visible);
568 /* one channel only */
569 break;
575 void
576 MidiTimeAxisView::build_controller_menu ()
578 using namespace Menu_Helpers;
580 if (controller_menu) {
581 /* it exists and has not been invalidated by a channel mode change, so just return it */
582 return;
585 controller_menu = new Menu; // explicitly managed by us
586 MenuList& items (controller_menu->items());
588 /* create several "top level" menu items for sets of controllers (16 at a time), and populate each one with a submenu
589 for each controller+channel combination covering the currently selected channels for this track
592 uint16_t selected_channels = _channel_selector.get_selected_channels();
594 /* count the number of selected channels because we will build a different menu structure if there is more than 1 selected.
597 int chn_cnt = 0;
599 for (uint8_t chn = 0; chn < 16; chn++) {
600 if (selected_channels & (0x0001 << chn)) {
601 if (++chn_cnt > 1) {
602 break;
607 /* loop over all 127 MIDI controllers, in groups of 16 */
609 for (int i = 0; i < 127; i += 16) {
611 Menu* ctl_menu = manage (new Menu);
612 MenuList& ctl_items (ctl_menu->items());
615 /* for each controller, consider whether to create a submenu or a single item */
617 for (int ctl = i; ctl < i+16; ++ctl) {
619 if (chn_cnt > 1) {
621 /* multiple channels - create a submenu, with 1 item per channel */
623 Menu* chn_menu = manage (new Menu);
624 MenuList& chn_items (chn_menu->items());
626 /* add a couple of items to hide/show this controller on all channels */
628 Evoral::Parameter param_without_channel (MidiCCAutomation, 0, ctl);
629 chn_items.push_back (MenuElem (_("Hide all channels"),
630 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
631 false, param_without_channel)));
632 chn_items.push_back (MenuElem (_("Show all channels"),
633 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::change_all_channel_tracks_visibility),
634 true, param_without_channel)));
636 for (uint8_t chn = 0; chn < 16; chn++) {
637 if (selected_channels & (0x0001 << chn)) {
639 /* for each selected channel, add a menu item for this controller */
641 Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
642 chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1),
643 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
644 fully_qualified_param)));
646 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
647 bool visible = false;
649 if (track) {
650 if (track->marked_for_display()) {
651 visible = true;
655 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&chn_items.back());
656 _controller_menu_map[fully_qualified_param] = cmi;
657 cmi->set_active (visible);
661 /* add the per-channel menu to the list of controllers, with the name of the controller */
662 ctl_items.push_back (MenuElem (string_compose ("<b>%1</b>: %2", ctl, midi_name (ctl)), *chn_menu));
663 dynamic_cast<Label*> (ctl_items.back().get_child())->set_use_markup (true);
665 } else {
667 /* just one channel - create a single menu item for this ctl+channel combination*/
669 for (uint8_t chn = 0; chn < 16; chn++) {
670 if (selected_channels & (0x0001 << chn)) {
672 Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
673 ctl_items.push_back (CheckMenuElem (_route->describe_parameter (fully_qualified_param),
674 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
675 fully_qualified_param)));
677 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
678 bool visible = false;
680 if (track) {
681 if (track->marked_for_display()) {
682 visible = true;
686 CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&ctl_items.back());
687 _controller_menu_map[fully_qualified_param] = cmi;
688 cmi->set_active (visible);
690 /* one channel only */
691 break;
697 /* add the menu for this block of controllers to the overall controller menu */
699 items.push_back (MenuElem (string_compose (_("Controllers %1-%2"), i, i+15), *ctl_menu));
703 Gtk::Menu*
704 MidiTimeAxisView::build_note_mode_menu()
706 using namespace Menu_Helpers;
708 Menu* mode_menu = manage (new Menu);
709 MenuList& items = mode_menu->items();
710 mode_menu->set_name ("ArdourContextMenu");
712 RadioMenuItem::Group mode_group;
713 items.push_back (RadioMenuElem (mode_group, _("Sustained"),
714 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_note_mode), Sustained)));
715 _note_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
716 _note_mode_item->set_active(_note_mode == Sustained);
718 items.push_back (RadioMenuElem (mode_group, _("Percussive"),
719 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_note_mode), Percussive)));
720 _percussion_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
721 _percussion_mode_item->set_active(_note_mode == Percussive);
723 return mode_menu;
726 Gtk::Menu*
727 MidiTimeAxisView::build_color_mode_menu()
729 using namespace Menu_Helpers;
731 Menu* mode_menu = manage (new Menu);
732 MenuList& items = mode_menu->items();
733 mode_menu->set_name ("ArdourContextMenu");
735 RadioMenuItem::Group mode_group;
736 items.push_back (RadioMenuElem (mode_group, _("Meter Colors"),
737 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode), MeterColors)));
738 _meter_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
739 _meter_color_mode_item->set_active(_color_mode == MeterColors);
741 items.push_back (RadioMenuElem (mode_group, _("Channel Colors"),
742 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode), ChannelColors)));
743 _channel_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
744 _channel_color_mode_item->set_active(_color_mode == ChannelColors);
746 items.push_back (RadioMenuElem (mode_group, _("Track Color"),
747 sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_color_mode), TrackColor)));
748 _channel_color_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
749 _channel_color_mode_item->set_active(_color_mode == TrackColor);
751 return mode_menu;
754 void
755 MidiTimeAxisView::set_note_mode(NoteMode mode)
757 if (_note_mode != mode || midi_track()->note_mode() != mode) {
758 _note_mode = mode;
759 midi_track()->set_note_mode(mode);
760 xml_node->add_property ("note-mode", enum_2_string(_note_mode));
761 _view->redisplay_track();
765 void
766 MidiTimeAxisView::set_color_mode(ColorMode mode)
768 if (_color_mode != mode) {
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 _view->redisplay_track();
781 void
782 MidiTimeAxisView::set_note_range(MidiStreamView::VisibleNoteRange range)
784 if (!_ignore_signals)
785 midi_view()->set_note_range(range);
789 void
790 MidiTimeAxisView::update_range()
792 MidiGhostRegion* mgr;
794 for(list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
795 if ((mgr = dynamic_cast<MidiGhostRegion*>(*i)) != 0) {
796 mgr->update_range();
801 void
802 MidiTimeAxisView::show_all_automation ()
804 if (midi_track()) {
805 const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
807 for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
808 create_automation_child(*i, true);
812 RouteTimeAxisView::show_all_automation ();
815 void
816 MidiTimeAxisView::show_existing_automation ()
818 if (midi_track()) {
819 const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation();
821 for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) {
822 create_automation_child(*i, true);
826 RouteTimeAxisView::show_existing_automation ();
829 /** Create an automation track for the given parameter (pitch bend, channel pressure).
831 void
832 MidiTimeAxisView::create_automation_child (const Evoral::Parameter& param, bool show)
834 /* These controllers are region "automation", so we do not create
835 * an AutomationList/Line for the track */
837 if (param.type() == NullAutomation) {
838 cerr << "WARNING: Attempt to create NullAutomation child, ignoring" << endl;
839 return;
842 AutomationTracks::iterator existing = _automation_tracks.find (param);
843 if (existing != _automation_tracks.end()) {
844 return;
847 boost::shared_ptr<AutomationControl> c = _route->get_control (param);
849 assert(c);
851 boost::shared_ptr<AutomationTimeAxisView> track(new AutomationTimeAxisView (_session,
852 _route, boost::shared_ptr<ARDOUR::Automatable>(), c,
853 _editor,
854 *this,
855 true,
856 parent_canvas,
857 _route->describe_parameter(param)));
859 add_automation_child (param, track, show);
863 void
864 MidiTimeAxisView::route_active_changed ()
866 RouteUI::route_active_changed ();
868 if (is_track()) {
869 if (_route->active()) {
870 controls_ebox.set_name ("MidiTrackControlsBaseUnselected");
871 controls_base_selected_name = "MidiTrackControlsBaseSelected";
872 controls_base_unselected_name = "MidiTrackControlsBaseUnselected";
873 } else {
874 controls_ebox.set_name ("MidiTrackControlsBaseInactiveUnselected");
875 controls_base_selected_name = "MidiTrackControlsBaseInactiveSelected";
876 controls_base_unselected_name = "MidiTrackControlsBaseInactiveUnselected";
878 } else {
880 throw; // wha?
882 if (_route->active()) {
883 controls_ebox.set_name ("BusControlsBaseUnselected");
884 controls_base_selected_name = "BusControlsBaseSelected";
885 controls_base_unselected_name = "BusControlsBaseUnselected";
886 } else {
887 controls_ebox.set_name ("BusControlsBaseInactiveUnselected");
888 controls_base_selected_name = "BusControlsBaseInactiveSelected";
889 controls_base_unselected_name = "BusControlsBaseInactiveUnselected";
894 void
895 MidiTimeAxisView::start_step_editing ()
897 step_edit_insert_position = _editor.get_preferred_edit_position ();
898 step_edit_beat_pos = -1.0;
899 _step_edit_triplet_countdown = 0;
900 _step_edit_within_chord = 0;
901 _step_edit_chord_duration = 0.0;
903 step_edit_region = playlist()->top_region_at (step_edit_insert_position);
905 if (step_edit_region) {
906 RegionView* rv = view()->find_view (step_edit_region);
907 step_edit_region_view = dynamic_cast<MidiRegionView*> (rv);
908 } else {
909 step_edit_region_view = 0;
913 if (step_editor == 0) {
914 step_editor = new StepEntry (*this);
915 step_editor->signal_delete_event().connect (sigc::mem_fun (*this, &MidiTimeAxisView::step_editor_hidden));
918 step_editor->set_position (WIN_POS_MOUSE);
919 step_editor->present ();
922 bool
923 MidiTimeAxisView::step_editor_hidden (GdkEventAny*)
925 /* everything else will follow the change in the model */
926 midi_track()->set_step_editing (false);
927 return true;
930 void
931 MidiTimeAxisView::stop_step_editing ()
933 if (step_editor) {
934 step_editor->hide ();
938 void
939 MidiTimeAxisView::check_step_edit ()
941 MidiRingBuffer<nframes_t>& incoming (midi_track()->step_edit_ring_buffer());
942 uint8_t* buf;
943 uint32_t bufsize = 32;
945 buf = new uint8_t[bufsize];
947 while (incoming.read_space()) {
948 nframes_t time;
949 Evoral::EventType type;
950 uint32_t size;
952 incoming.read_prefix (&time, &type, &size);
954 if (size > bufsize) {
955 delete [] buf;
956 bufsize = size;
957 buf = new uint8_t[bufsize];
960 incoming.read_contents (size, buf);
962 if ((buf[0] & 0xf0) == MIDI_CMD_NOTE_ON) {
963 step_add_note (buf[0] & 0xf, buf[1], buf[2], 0.0);
969 MidiTimeAxisView::step_add_note (uint8_t channel, uint8_t pitch, uint8_t velocity, Evoral::MusicalTime beat_duration)
972 if (step_edit_region == 0) {
974 step_edit_region = add_region (step_edit_insert_position);
975 RegionView* rv = view()->find_view (step_edit_region);
976 step_edit_region_view = dynamic_cast<MidiRegionView*>(rv);
979 if (step_edit_region && step_edit_region_view) {
980 if (step_edit_beat_pos < 0.0) {
981 framecnt_t frames_from_start = _editor.get_preferred_edit_position() - step_edit_region->position();
982 if (frames_from_start < 0) {
983 return 1;
985 step_edit_beat_pos = step_edit_region_view->frames_to_beats (frames_from_start);
988 if (beat_duration == 0.0) {
989 bool success;
990 beat_duration = _editor.get_grid_type_as_beats (success, step_edit_insert_position);
992 if (!success) {
993 return -1;
997 MidiStreamView* msv = midi_view();
999 /* make sure its visible on the vertical axis */
1001 if (pitch < msv->lowest_note() || pitch > msv->highest_note()) {
1002 msv->update_note_range (pitch);
1003 msv->set_note_range (MidiStreamView::ContentsRange);
1006 /* make sure its visible on the horizontal axis */
1008 nframes64_t fpos = step_edit_region->position() +
1009 step_edit_region_view->beats_to_frames (step_edit_beat_pos + beat_duration);
1011 if (fpos >= (_editor.leftmost_position() + _editor.current_page_frames())) {
1012 _editor.reset_x_origin (fpos - (_editor.current_page_frames()/4));
1015 step_edit_region_view->step_add_note (channel, pitch, velocity, step_edit_beat_pos, beat_duration);
1017 if (_step_edit_triplet_countdown > 0) {
1018 _step_edit_triplet_countdown--;
1020 if (_step_edit_triplet_countdown == 0) {
1021 _step_edit_triplet_countdown = 3;
1025 if (!_step_edit_within_chord) {
1026 step_edit_beat_pos += beat_duration;
1027 } else {
1028 step_edit_beat_pos += 1.0/Meter::ticks_per_beat; // tiny, but no longer overlapping
1029 _step_edit_chord_duration = beat_duration;
1033 return 0;
1036 bool
1037 MidiTimeAxisView::step_edit_within_triplet() const
1039 return _step_edit_triplet_countdown > 0;
1042 bool
1043 MidiTimeAxisView::step_edit_within_chord() const
1045 return _step_edit_within_chord;
1048 void
1049 MidiTimeAxisView::step_edit_toggle_triplet ()
1051 if (_step_edit_triplet_countdown == 0) {
1052 _step_edit_within_chord = false;
1053 _step_edit_triplet_countdown = 3;
1054 } else {
1055 _step_edit_triplet_countdown = 0;
1059 void
1060 MidiTimeAxisView::step_edit_toggle_chord ()
1062 if (_step_edit_within_chord) {
1063 _step_edit_within_chord = false;
1064 step_edit_beat_pos += _step_edit_chord_duration;
1065 } else {
1066 _step_edit_triplet_countdown = 0;
1067 _step_edit_within_chord = true;
1071 void
1072 MidiTimeAxisView::step_edit_rest (Evoral::MusicalTime beats)
1074 bool success;
1076 if (beats == 0.0) {
1077 beats = _editor.get_grid_type_as_beats (success, step_edit_insert_position);
1078 } else {
1079 success = true;
1082 if (success) {
1083 step_edit_beat_pos += beats;
1087 boost::shared_ptr<Region>
1088 MidiTimeAxisView::add_region (framepos_t pos)
1090 Editor* real_editor = dynamic_cast<Editor*> (&_editor);
1092 real_editor->begin_reversible_command (_("create region"));
1093 playlist()->clear_history ();
1095 framepos_t start = pos;
1096 real_editor->snap_to (start, -1);
1097 const Meter& m = _session->tempo_map().meter_at(start);
1098 const Tempo& t = _session->tempo_map().tempo_at(start);
1099 double length = floor (m.frames_per_bar(t, _session->frame_rate()));
1101 boost::shared_ptr<Source> src = _session->create_midi_source_for_session (view()->trackview().track().get(),
1102 view()->trackview().track()->name());
1103 PropertyList plist;
1105 plist.add (ARDOUR::Properties::start, 0);
1106 plist.add (ARDOUR::Properties::length, length);
1107 plist.add (ARDOUR::Properties::name, PBD::basename_nosuffix(src->name()));
1109 boost::shared_ptr<Region> region = (RegionFactory::create (src, plist));
1111 playlist()->add_region (region, start);
1112 _session->add_command (new StatefulDiffCommand (playlist()));
1114 real_editor->commit_reversible_command();
1116 return region;
1119 void
1120 MidiTimeAxisView::add_note_selection (uint8_t note)
1122 if (!_editor.internal_editing()) {
1123 return;
1126 uint16_t chn_mask = _channel_selector.get_selected_channels();
1128 if (_view->num_selected_regionviews() == 0) {
1129 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask));
1130 } else {
1131 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask));
1135 void
1136 MidiTimeAxisView::extend_note_selection (uint8_t note)
1138 if (!_editor.internal_editing()) {
1139 return;
1142 uint16_t chn_mask = _channel_selector.get_selected_channels();
1144 if (_view->num_selected_regionviews() == 0) {
1145 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask));
1146 } else {
1147 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask));
1151 void
1152 MidiTimeAxisView::toggle_note_selection (uint8_t note)
1154 if (!_editor.internal_editing()) {
1155 return;
1158 uint16_t chn_mask = _channel_selector.get_selected_channels();
1160 if (_view->num_selected_regionviews() == 0) {
1161 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask));
1162 } else {
1163 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask));
1167 void
1168 MidiTimeAxisView::add_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
1170 dynamic_cast<MidiRegionView*>(rv)->select_matching_notes (note, chn_mask, false, false);
1173 void
1174 MidiTimeAxisView::extend_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
1176 dynamic_cast<MidiRegionView*>(rv)->select_matching_notes (note, chn_mask, true, true);
1179 void
1180 MidiTimeAxisView::toggle_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
1182 dynamic_cast<MidiRegionView*>(rv)->toggle_matching_notes (note, chn_mask);
1185 void
1186 MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t)
1188 /* hide all automation tracks that use the wrong channel(s) and show all those that use
1189 the right ones.
1192 uint16_t selected_channels = _channel_selector.get_selected_channels();
1193 bool changed = false;
1195 no_redraw = true;
1197 for (uint32_t ctl = 0; ctl < 127; ++ctl) {
1199 for (uint32_t chn = 0; chn < 16; ++chn) {
1200 Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
1201 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
1203 if (!track) {
1204 continue;
1207 if ((selected_channels & (0x0001 << chn)) == 0) {
1208 /* channel not in use. hiding it will trigger RouteTimeAxisView::automation_track_hidden()
1209 which will cause a redraw. We don't want one per channel, so block that with no_redraw.
1211 changed = track->set_visibility (false) || changed;
1212 } else {
1213 changed = track->set_visibility (true) || changed;
1218 no_redraw = false;
1220 /* TODO: Bender, PgmChange, Pressure */
1222 /* invalidate the controller menu, so that we rebuilt it next time */
1223 _controller_menu_map.clear ();
1224 delete controller_menu;
1225 controller_menu = 0;
1227 if (changed) {
1228 _route->gui_changed ("track_height", this);
1232 Gtk::CheckMenuItem*
1233 MidiTimeAxisView::automation_child_menu_item (Evoral::Parameter param)
1235 Gtk::CheckMenuItem* m = RouteTimeAxisView::automation_child_menu_item (param);
1236 if (m) {
1237 return m;
1240 ParameterMenuMap::iterator i = _controller_menu_map.find (param);
1241 if (i != _controller_menu_map.end()) {
1242 return i->second;
1245 i = _channel_command_menu_map.find (param);
1246 if (i != _channel_command_menu_map.end()) {
1247 return i->second;
1250 return 0;