2 Copyright (C) 2015 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.
20 #include <gtkmm/alignment.h>
21 #include <gtkmm/label.h>
22 #include <gtkmm/liststore.h>
24 #include "pbd/unwind.h"
25 #include "pbd/strsplit.h"
26 #include "pbd/file_utils.h"
28 #include "gtkmm2ext/bindings.h"
29 #include "gtkmm2ext/gui_thread.h"
30 #include "gtkmm2ext/utils.h"
32 #include "ardour/audioengine.h"
33 #include "ardour/filesystem_paths.h"
35 #include "evoral/midi_util.h"
43 using namespace ARDOUR;
44 using namespace ArdourSurface;
47 using namespace Gtkmm2ext;
50 Push2::get_gui () const
53 const_cast<Push2*>(this)->build_gui ();
55 static_cast<Gtk::VBox*>(gui)->show_all();
60 Push2::tear_down_gui ()
63 Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
76 gui = new P2GUI (*this);
79 /*--------------------*/
81 P2GUI::P2GUI (Push2& p)
85 , ignore_active_change (false)
87 , root_note_octave_adjustment (3, 0, 10, 1, 1)
88 , root_note_octave (root_note_octave_adjustment)
89 , root_note_octave_label (X_("Octave"))
90 , root_note_label (X_("Root"))
91 , mode_label (X_("Mode (Scale)"))
94 set_border_width (12);
96 table.set_row_spacings (4);
97 table.set_col_spacings (6);
98 table.set_border_width (12);
99 table.set_homogeneous (false);
101 std::string data_file_path;
102 string name = "push2-small.png";
103 Searchpath spath(ARDOUR::ardour_data_search_path());
104 spath.add_subdirectory_to_paths ("icons");
105 find_file (spath, name, data_file_path);
106 if (!data_file_path.empty()) {
107 image.set (data_file_path);
108 hpacker.pack_start (image, false, false);
114 input_combo.pack_start (midi_port_columns.short_name);
115 output_combo.pack_start (midi_port_columns.short_name);
117 input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &P2GUI::active_port_changed), &input_combo, true));
118 output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &P2GUI::active_port_changed), &output_combo, false));
120 l = manage (new Gtk::Label);
121 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
122 l->set_alignment (1.0, 0.5);
123 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
124 table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
127 l = manage (new Gtk::Label);
128 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
129 l->set_alignment (1.0, 0.5);
130 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
131 table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
134 hpacker.pack_start (table, true, true);
136 pad_table.set_spacings (3);
139 root_note_selector.set_model (build_note_columns());
140 root_note_selector.pack_start (note_columns.name);
141 root_note_selector.set_active (0);
143 mode_selector.set_model (build_mode_columns());
144 mode_selector.pack_start (mode_columns.name);
145 mode_selector.set_active (0);
147 mode_packer.set_border_width (12);
148 mode_packer.set_spacings (12);
150 mode_packer.attach (root_note_label, 0, 1, 0, 1, AttachOptions (FILL|EXPAND), SHRINK);
151 mode_packer.attach (root_note_selector, 1, 2, 0, 1, AttachOptions (FILL|EXPAND), SHRINK);
153 mode_packer.attach (root_note_octave_label, 0, 1, 1, 2, AttachOptions (FILL|EXPAND), SHRINK);
154 mode_packer.attach (root_note_octave, 1, 2, 1, 2, AttachOptions (FILL|EXPAND), SHRINK);
156 mode_packer.attach (mode_label, 0, 1, 2, 3, AttachOptions (FILL|EXPAND), SHRINK);
157 mode_packer.attach (mode_selector, 1, 2, 2, 3, AttachOptions (FILL|EXPAND), SHRINK);
159 pad_notebook.append_page (pad_table, _("Pad Layout"));
160 pad_notebook.append_page (mode_packer, _("Modes/Scales"));
161 pad_notebook.append_page (custom_packer, _("Custom"));
163 root_note_octave_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &P2GUI::reprogram_pad_scale));
164 root_note_selector.signal_changed().connect (sigc::mem_fun (*this, &P2GUI::reprogram_pad_scale));
165 mode_selector.signal_changed().connect (sigc::mem_fun (*this, &P2GUI::reprogram_pad_scale));
169 pack_start (hpacker, false, false);
170 pack_start (pad_notebook);
172 /* update the port connection combos */
174 update_port_combos ();
176 /* catch future changes to connection state */
178 // p2.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&P2GUI::connection_handler, this), gui_context());
179 p2.PadChange.connect (p2_connections, invalidator (*this), boost::bind (&P2GUI::build_pad_table, this), gui_context());
187 P2GUI::connection_handler ()
189 /* ignore all changes to combobox active strings here, because we're
190 updating them to match a new ("external") reality - we were called
191 because port connections have changed.
194 PBD::Unwinder<bool> ici (ignore_active_change, true);
196 update_port_combos ();
200 P2GUI::update_port_combos ()
202 vector<string> midi_inputs;
203 vector<string> midi_outputs;
205 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
206 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
208 Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
209 Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
210 bool input_found = false;
211 bool output_found = false;
214 input_combo.set_model (input);
215 output_combo.set_model (output);
217 Gtk::TreeModel::Children children = input->children();
218 Gtk::TreeModel::Children::iterator i;
219 i = children.begin();
220 ++i; /* skip "Disconnected" */
223 for (n = 1; i != children.end(); ++i, ++n) {
224 string port_name = (*i)[midi_port_columns.full_name];
225 if (p2.input_port()->connected_to (port_name)) {
226 input_combo.set_active (n);
233 input_combo.set_active (0); /* disconnected */
236 children = output->children();
237 i = children.begin();
238 ++i; /* skip "Disconnected" */
240 for (n = 1; i != children.end(); ++i, ++n) {
241 string port_name = (*i)[midi_port_columns.full_name];
242 if (p2.output_port()->connected_to (port_name)) {
243 output_combo.set_active (n);
250 output_combo.set_active (0); /* disconnected */
255 P2GUI::build_available_action_menu ()
257 /* build a model of all available actions (needs to be tree structured
261 available_action_model = TreeStore::create (action_columns);
263 vector<string> paths;
264 vector<string> labels;
265 vector<string> tooltips;
267 vector<Glib::RefPtr<Gtk::Action> > actions;
269 Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
271 typedef std::map<string,TreeIter> NodeMap;
276 vector<string>::iterator k;
277 vector<string>::iterator p;
278 vector<string>::iterator t;
279 vector<string>::iterator l;
281 available_action_model->clear ();
284 TreeModel::Row parent;
286 /* Disabled item (row 0) */
288 rowp = available_action_model->append();
290 parent[action_columns.name] = _("Disabled");
294 rowp = available_action_model->append();
296 parent[action_columns.name] = _("Shift");
297 rowp = available_action_model->append();
299 parent[action_columns.name] = _("Control");
300 rowp = available_action_model->append();
302 parent[action_columns.name] = _("Option");
303 rowp = available_action_model->append();
305 parent[action_columns.name] = _("CmdAlt");
308 for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
311 vector<string> parts;
315 split (*p, parts, '/');
321 //kinda kludgy way to avoid displaying menu items as mappable
322 if ( parts[1] == _("Main_menu") )
324 if ( parts[1] == _("JACK") )
326 if ( parts[1] == _("redirectmenu") )
328 if ( parts[1] == _("Editor_menus") )
330 if ( parts[1] == _("RegionList") )
332 if ( parts[1] == _("ProcessorMenu") )
335 if ((r = nodes.find (parts[1])) == nodes.end()) {
337 /* top level is missing */
340 TreeModel::Row parent;
341 rowp = available_action_model->append();
342 nodes[parts[1]] = rowp;
344 parent[action_columns.name] = parts[1];
346 row = *(available_action_model->append (parent.children()));
350 row = *(available_action_model->append ((*r->second)->children()));
354 /* add this action */
357 row[action_columns.name] = *t;
360 row[action_columns.name] = *l;
365 /* ControlProtocol::access_action() is not interested in the
366 legacy "<Actions>/" prefix part of a path.
368 path = path.substr (strlen ("<Actions>/"));
370 row[action_columns.path] = path;
376 P2GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const & action_path, TreeModel::iterator* found)
378 TreeModel::Row row = *iter;
379 string path = row[action_columns.path];
381 if (path == action_path) {
389 Glib::RefPtr<Gtk::ListStore>
390 P2GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
392 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
395 row = *store->append ();
396 row[midi_port_columns.full_name] = string();
397 row[midi_port_columns.short_name] = _("Disconnected");
399 for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
400 row = *store->append ();
401 row[midi_port_columns.full_name] = *p;
402 std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
404 pn = (*p).substr ((*p).find (':') + 1);
406 row[midi_port_columns.short_name] = pn;
413 P2GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
415 if (ignore_active_change) {
419 TreeModel::iterator active = combo->get_active ();
420 string new_port = (*active)[midi_port_columns.full_name];
422 if (new_port.empty()) {
424 p2.input_port()->disconnect_all ();
426 p2.output_port()->disconnect_all ();
433 if (!p2.input_port()->connected_to (new_port)) {
434 p2.input_port()->disconnect_all ();
435 p2.input_port()->connect (new_port);
438 if (!p2.output_port()->connected_to (new_port)) {
439 p2.output_port()->disconnect_all ();
440 p2.output_port()->connect (new_port);
446 P2GUI::build_pad_table ()
448 container_clear (pad_table);
450 for (int row = 0; row < 8; ++row) {
451 for (int col = 0; col < 8; ++col) {
453 int n = (int) p2.pad_note (row, col);
455 Gtk::Button* b = manage (new Button (string_compose ("%1 (%2)", Evoral::midi_note_name (n), n)));
458 pad_table.attach (*b, col, col+1, row, row + 1);
463 Glib::RefPtr<Gtk::ListStore>
464 P2GUI::build_mode_columns ()
466 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (mode_columns);
469 row = *store->append();
470 row[mode_columns.name] = _("Dorian");
471 row[mode_columns.mode] = MusicalMode::Dorian;
473 row = *store->append();
474 row[mode_columns.name] = _("Ionian (\"Major\")");
475 row[mode_columns.mode] = MusicalMode::IonianMajor;
477 row = *store->append();
478 row[mode_columns.name] = _("Minor");
479 row[mode_columns.mode] = MusicalMode::Minor;
481 row = *store->append();
482 row[mode_columns.name] = _("Harmonic Minor");
483 row[mode_columns.mode] = MusicalMode::HarmonicMinor;
485 row = *store->append();
486 row[mode_columns.name] = _("Melodic Minor Ascending");
487 row[mode_columns.mode] = MusicalMode::MelodicMinorAscending;
489 row = *store->append();
490 row[mode_columns.name] = _("Melodic Minor Descending");
491 row[mode_columns.mode] = MusicalMode::MelodicMinorDescending;
493 row = *store->append();
494 row[mode_columns.name] = _("Phrygian");
495 row[mode_columns.mode] = MusicalMode::Phrygian;
497 row = *store->append();
498 row[mode_columns.name] = _("Lydian");
499 row[mode_columns.mode] = MusicalMode::Lydian;
501 row = *store->append();
502 row[mode_columns.name] = _("Mixolydian");
503 row[mode_columns.mode] = MusicalMode::Mixolydian;
505 row = *store->append();
506 row[mode_columns.name] = _("Aeolian (\"Major\")");
507 row[mode_columns.mode] = MusicalMode::Aeolian;
509 row = *store->append();
510 row[mode_columns.name] = _("Locrian");
511 row[mode_columns.mode] = MusicalMode::Locrian;
513 row = *store->append();
514 row[mode_columns.name] = _("Pentatonic Major");
515 row[mode_columns.mode] = MusicalMode::PentatonicMajor;
517 row = *store->append();
518 row[mode_columns.name] = _("Pentatonic Minor");
519 row[mode_columns.mode] = MusicalMode::PentatonicMinor;
521 row = *store->append();
522 row[mode_columns.name] = _("Chromatic");
523 row[mode_columns.mode] = MusicalMode::Chromatic;
525 row = *store->append();
526 row[mode_columns.name] = _("Blues Scale");
527 row[mode_columns.mode] = MusicalMode::BluesScale;
529 row = *store->append();
530 row[mode_columns.name] = _("Neapolitan Minor");
531 row[mode_columns.mode] = MusicalMode::NeapolitanMinor;
533 row = *store->append();
534 row[mode_columns.name] = _("Neapolitan Major");
535 row[mode_columns.mode] = MusicalMode::NeapolitanMajor;
537 row = *store->append();
538 row[mode_columns.name] = _("Oriental");
539 row[mode_columns.mode] = MusicalMode::Oriental;
541 row = *store->append();
542 row[mode_columns.name] = _("Double Harmonic");
543 row[mode_columns.mode] = MusicalMode::DoubleHarmonic;
545 row = *store->append();
546 row[mode_columns.name] = _("Enigmatic");
547 row[mode_columns.mode] = MusicalMode::Enigmatic;
549 row = *store->append();
550 row[mode_columns.name] = _("Hirajoshi");
551 row[mode_columns.mode] = MusicalMode::Hirajoshi;
553 row = *store->append();
554 row[mode_columns.name] = _("Hungarian Minor");
555 row[mode_columns.mode] = MusicalMode::HungarianMinor;
557 row = *store->append();
558 row[mode_columns.name] = _("Hungarian Major");
559 row[mode_columns.mode] = MusicalMode::HungarianMajor;
561 row = *store->append();
562 row[mode_columns.name] = _("Kumoi");
563 row[mode_columns.mode] = MusicalMode::Kumoi;
565 row = *store->append();
566 row[mode_columns.name] = _("Iwato");
567 row[mode_columns.mode] = MusicalMode::Iwato;
569 row = *store->append();
570 row[mode_columns.name] = _("Hindu");
571 row[mode_columns.mode] = MusicalMode::Hindu;
573 row = *store->append();
574 row[mode_columns.name] = _("Spanish 8 Tone");
575 row[mode_columns.mode] = MusicalMode::Spanish8Tone;
577 row = *store->append();
578 row[mode_columns.name] = _("Pelog");
579 row[mode_columns.mode] = MusicalMode::Pelog;
581 row = *store->append();
582 row[mode_columns.name] = _("Hungarian Gypsy");
583 row[mode_columns.mode] = MusicalMode::HungarianGypsy;
585 row = *store->append();
586 row[mode_columns.name] = _("Overtone");
587 row[mode_columns.mode] = MusicalMode::Overtone;
589 row = *store->append();
590 row[mode_columns.name] = _("Leading Whole Tone");
591 row[mode_columns.mode] = MusicalMode::LeadingWholeTone;
593 row = *store->append();
594 row[mode_columns.name] = _("Arabian");
595 row[mode_columns.mode] = MusicalMode::Arabian;
597 row = *store->append();
598 row[mode_columns.name] = _("Balinese");
599 row[mode_columns.mode] = MusicalMode::Balinese;
601 row = *store->append();
602 row[mode_columns.name] = _("Gypsy");
603 row[mode_columns.mode] = MusicalMode::Gypsy;
605 row = *store->append();
606 row[mode_columns.name] = _("Mohammedan");
607 row[mode_columns.mode] = MusicalMode::Mohammedan;
609 row = *store->append();
610 row[mode_columns.name] = _("Javanese");
611 row[mode_columns.mode] = MusicalMode::Javanese;
613 row = *store->append();
614 row[mode_columns.name] = _("Persian");
615 row[mode_columns.mode] = MusicalMode::Persian;
617 row = *store->append();
618 row[mode_columns.name] = _("Algerian");
619 row[mode_columns.mode] = MusicalMode::Algerian;
624 Glib::RefPtr<Gtk::ListStore>
625 P2GUI::build_note_columns ()
627 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (note_columns);
630 row = *store->append ();
631 row[note_columns.number] = 0;
632 row[note_columns.name] = "C";
634 row = *store->append ();
635 row[note_columns.number] = 1;
636 row[note_columns.name] = "C#";
638 row = *store->append ();
639 row[note_columns.number] = 2;
640 row[note_columns.name] = "D";
642 row = *store->append ();
643 row[note_columns.number] = 3;
644 row[note_columns.name] = "D#";
646 row = *store->append ();
647 row[note_columns.number] = 4;
648 row[note_columns.name] = "E";
650 row = *store->append ();
651 row[note_columns.number] = 5;
652 row[note_columns.name] = "F";
654 row = *store->append ();
655 row[note_columns.number] = 6;
656 row[note_columns.name] = "F#";
658 row = *store->append ();
659 row[note_columns.number] = 7;
660 row[note_columns.name] = "G";
662 row = *store->append ();
663 row[note_columns.number] = 8;
664 row[note_columns.name] = "G#";
666 row = *store->append ();
667 row[note_columns.number] = 9;
668 row[note_columns.name] = "A";
670 row = *store->append ();
671 row[note_columns.number] = 10;
672 row[note_columns.name] = "A#";
674 row = *store->append ();
675 row[note_columns.number] = 11;
676 row[note_columns.name] = "B";
682 P2GUI::reprogram_pad_scale ()
686 MusicalMode::Type mode;
688 Gtk::TreeModel::iterator iter = root_note_selector.get_active();
690 Gtk::TreeModel::Row row = *iter;
692 root = row[note_columns.number];
700 octave = (int) floor (root_note_octave_adjustment.get_value ());
702 iter = mode_selector.get_active();
704 Gtk::TreeModel::Row row = *iter;
706 mode = row[mode_columns.mode];
708 mode = MusicalMode::IonianMajor;
711 mode = MusicalMode::IonianMajor;
714 p2.set_pad_scale (root, octave, mode);