fix up initial step edit pos when operating on an existing region
[ardour2.git] / gtk2_ardour / midi_time_axis.cc
blob79e53a1faf6ebaf98316e18d9125892bc9aece8d
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_triplet_countdown = 0;
899 _step_edit_within_chord = 0;
900 _step_edit_chord_duration = 0.0;
902 step_edit_region = playlist()->top_region_at (step_edit_insert_position);
904 if (step_edit_region) {
905 RegionView* rv = view()->find_view (step_edit_region);
906 step_edit_region_view = dynamic_cast<MidiRegionView*> (rv);
908 } else {
909 step_edit_region = add_region (step_edit_insert_position);
910 RegionView* rv = view()->find_view (step_edit_region);
911 step_edit_region_view = dynamic_cast<MidiRegionView*>(rv);
914 assert (step_edit_region);
915 assert (step_edit_region_view);
917 if (step_editor == 0) {
918 step_editor = new StepEntry (*this);
919 step_editor->signal_delete_event().connect (sigc::mem_fun (*this, &MidiTimeAxisView::step_editor_hidden));
920 step_editor->signal_hide().connect (sigc::mem_fun (*this, &MidiTimeAxisView::step_editor_hide));
923 framecnt_t frames_from_start = _editor.get_preferred_edit_position() - step_edit_region->position();
925 assert (frames_from_start >= 0);
927 step_edit_beat_pos = step_edit_region_view->frames_to_beats (frames_from_start);
929 step_edit_region_view->show_step_edit_cursor (step_edit_beat_pos);
930 step_edit_region_view->set_step_edit_cursor_width (step_editor->note_length());
932 step_editor->set_position (WIN_POS_MOUSE);
933 step_editor->present ();
936 bool
937 MidiTimeAxisView::step_editor_hidden (GdkEventAny*)
939 step_editor_hide ();
940 return true;
943 void
944 MidiTimeAxisView::step_editor_hide ()
946 /* everything else will follow the change in the model */
947 midi_track()->set_step_editing (false);
950 void
951 MidiTimeAxisView::stop_step_editing ()
953 if (step_editor) {
954 step_editor->hide ();
957 if (step_edit_region_view) {
958 step_edit_region_view->hide_step_edit_cursor();
962 void
963 MidiTimeAxisView::check_step_edit ()
965 MidiRingBuffer<nframes_t>& incoming (midi_track()->step_edit_ring_buffer());
966 uint8_t* buf;
967 uint32_t bufsize = 32;
969 buf = new uint8_t[bufsize];
971 while (incoming.read_space()) {
972 nframes_t time;
973 Evoral::EventType type;
974 uint32_t size;
976 incoming.read_prefix (&time, &type, &size);
978 if (size > bufsize) {
979 delete [] buf;
980 bufsize = size;
981 buf = new uint8_t[bufsize];
984 incoming.read_contents (size, buf);
986 if ((buf[0] & 0xf0) == MIDI_CMD_NOTE_ON) {
987 step_add_note (buf[0] & 0xf, buf[1], buf[2], 0.0);
993 MidiTimeAxisView::step_add_bank_change (uint8_t channel, uint8_t bank)
995 return 0;
999 MidiTimeAxisView::step_add_program_change (uint8_t channel, uint8_t program)
1001 return 0;
1005 MidiTimeAxisView::step_add_note (uint8_t channel, uint8_t pitch, uint8_t velocity, Evoral::MusicalTime beat_duration)
1007 if (step_edit_region && step_edit_region_view) {
1009 if (beat_duration == 0.0) {
1010 bool success;
1011 beat_duration = _editor.get_grid_type_as_beats (success, step_edit_insert_position);
1013 if (!success) {
1014 return -1;
1018 MidiStreamView* msv = midi_view();
1020 /* make sure its visible on the vertical axis */
1022 if (pitch < msv->lowest_note() || pitch > msv->highest_note()) {
1023 msv->update_note_range (pitch);
1024 msv->set_note_range (MidiStreamView::ContentsRange);
1027 /* make sure its visible on the horizontal axis */
1029 nframes64_t fpos = step_edit_region->position() +
1030 step_edit_region_view->beats_to_frames (step_edit_beat_pos + beat_duration);
1032 if (fpos >= (_editor.leftmost_position() + _editor.current_page_frames())) {
1033 _editor.reset_x_origin (fpos - (_editor.current_page_frames()/4));
1036 step_edit_region_view->step_add_note (channel, pitch, velocity, step_edit_beat_pos, beat_duration);
1038 if (_step_edit_triplet_countdown > 0) {
1039 _step_edit_triplet_countdown--;
1041 if (_step_edit_triplet_countdown == 0) {
1042 _step_edit_triplet_countdown = 3;
1046 if (!_step_edit_within_chord) {
1047 step_edit_beat_pos += beat_duration;
1048 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
1049 } else {
1050 step_edit_beat_pos += 1.0/Meter::ticks_per_beat; // tiny, but no longer overlapping
1051 _step_edit_chord_duration = max (_step_edit_chord_duration, beat_duration);
1055 return 0;
1058 void
1059 MidiTimeAxisView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
1061 if (step_edit_region_view) {
1062 step_edit_region_view->set_step_edit_cursor_width (beats);
1066 bool
1067 MidiTimeAxisView::step_edit_within_triplet() const
1069 return _step_edit_triplet_countdown > 0;
1072 bool
1073 MidiTimeAxisView::step_edit_within_chord() const
1075 return _step_edit_within_chord;
1078 void
1079 MidiTimeAxisView::step_edit_toggle_triplet ()
1081 if (_step_edit_triplet_countdown == 0) {
1082 _step_edit_within_chord = false;
1083 _step_edit_triplet_countdown = 3;
1084 } else {
1085 _step_edit_triplet_countdown = 0;
1089 void
1090 MidiTimeAxisView::step_edit_toggle_chord ()
1092 if (_step_edit_within_chord) {
1093 _step_edit_within_chord = false;
1094 step_edit_beat_pos += _step_edit_chord_duration;
1095 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
1096 } else {
1097 _step_edit_triplet_countdown = 0;
1098 _step_edit_within_chord = true;
1102 void
1103 MidiTimeAxisView::step_edit_rest (Evoral::MusicalTime beats)
1105 bool success;
1107 if (beats == 0.0) {
1108 beats = _editor.get_grid_type_as_beats (success, step_edit_insert_position);
1109 } else {
1110 success = true;
1113 if (success) {
1114 step_edit_beat_pos += beats;
1115 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
1119 void
1120 MidiTimeAxisView::step_edit_beat_sync ()
1122 step_edit_beat_pos = ceil (step_edit_beat_pos);
1123 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
1126 void
1127 MidiTimeAxisView::step_edit_bar_sync ()
1129 if (!_session || !step_edit_region_view || !step_edit_region) {
1130 return;
1133 nframes64_t fpos = step_edit_region->position() +
1134 step_edit_region_view->beats_to_frames (step_edit_beat_pos);
1135 fpos = _session->tempo_map().round_to_bar (fpos, 1);
1136 step_edit_beat_pos = ceil (step_edit_region_view->frames_to_beats (fpos - step_edit_region->position()));
1137 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
1140 boost::shared_ptr<Region>
1141 MidiTimeAxisView::add_region (framepos_t pos)
1143 Editor* real_editor = dynamic_cast<Editor*> (&_editor);
1145 real_editor->begin_reversible_command (_("create region"));
1146 playlist()->clear_history ();
1148 framepos_t start = pos;
1149 real_editor->snap_to (start, -1);
1150 const Meter& m = _session->tempo_map().meter_at(start);
1151 const Tempo& t = _session->tempo_map().tempo_at(start);
1152 double length = floor (m.frames_per_bar(t, _session->frame_rate()));
1154 boost::shared_ptr<Source> src = _session->create_midi_source_for_session (view()->trackview().track().get(),
1155 view()->trackview().track()->name());
1156 PropertyList plist;
1158 plist.add (ARDOUR::Properties::start, 0);
1159 plist.add (ARDOUR::Properties::length, length);
1160 plist.add (ARDOUR::Properties::name, PBD::basename_nosuffix(src->name()));
1162 boost::shared_ptr<Region> region = (RegionFactory::create (src, plist));
1164 playlist()->add_region (region, start);
1165 _session->add_command (new StatefulDiffCommand (playlist()));
1167 real_editor->commit_reversible_command();
1169 return region;
1172 void
1173 MidiTimeAxisView::add_note_selection (uint8_t note)
1175 if (!_editor.internal_editing()) {
1176 return;
1179 uint16_t chn_mask = _channel_selector.get_selected_channels();
1181 if (_view->num_selected_regionviews() == 0) {
1182 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask));
1183 } else {
1184 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask));
1188 void
1189 MidiTimeAxisView::extend_note_selection (uint8_t note)
1191 if (!_editor.internal_editing()) {
1192 return;
1195 uint16_t chn_mask = _channel_selector.get_selected_channels();
1197 if (_view->num_selected_regionviews() == 0) {
1198 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask));
1199 } else {
1200 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask));
1204 void
1205 MidiTimeAxisView::toggle_note_selection (uint8_t note)
1207 if (!_editor.internal_editing()) {
1208 return;
1211 uint16_t chn_mask = _channel_selector.get_selected_channels();
1213 if (_view->num_selected_regionviews() == 0) {
1214 _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask));
1215 } else {
1216 _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask));
1220 void
1221 MidiTimeAxisView::add_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
1223 dynamic_cast<MidiRegionView*>(rv)->select_matching_notes (note, chn_mask, false, false);
1226 void
1227 MidiTimeAxisView::extend_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
1229 dynamic_cast<MidiRegionView*>(rv)->select_matching_notes (note, chn_mask, true, true);
1232 void
1233 MidiTimeAxisView::toggle_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask)
1235 dynamic_cast<MidiRegionView*>(rv)->toggle_matching_notes (note, chn_mask);
1238 void
1239 MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t)
1241 /* hide all automation tracks that use the wrong channel(s) and show all those that use
1242 the right ones.
1245 uint16_t selected_channels = _channel_selector.get_selected_channels();
1246 bool changed = false;
1248 no_redraw = true;
1250 for (uint32_t ctl = 0; ctl < 127; ++ctl) {
1252 for (uint32_t chn = 0; chn < 16; ++chn) {
1253 Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
1254 boost::shared_ptr<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
1256 if (!track) {
1257 continue;
1260 if ((selected_channels & (0x0001 << chn)) == 0) {
1261 /* channel not in use. hiding it will trigger RouteTimeAxisView::automation_track_hidden()
1262 which will cause a redraw. We don't want one per channel, so block that with no_redraw.
1264 changed = track->set_visibility (false) || changed;
1265 } else {
1266 changed = track->set_visibility (true) || changed;
1271 no_redraw = false;
1273 /* TODO: Bender, PgmChange, Pressure */
1275 /* invalidate the controller menu, so that we rebuilt it next time */
1276 _controller_menu_map.clear ();
1277 delete controller_menu;
1278 controller_menu = 0;
1280 if (changed) {
1281 _route->gui_changed ("track_height", this);
1285 Gtk::CheckMenuItem*
1286 MidiTimeAxisView::automation_child_menu_item (Evoral::Parameter param)
1288 Gtk::CheckMenuItem* m = RouteTimeAxisView::automation_child_menu_item (param);
1289 if (m) {
1290 return m;
1293 ParameterMenuMap::iterator i = _controller_menu_map.find (param);
1294 if (i != _controller_menu_map.end()) {
1295 return i->second;
1298 i = _channel_command_menu_map.find (param);
1299 if (i != _channel_command_menu_map.end()) {
1300 return i->second;
1303 return 0;