2 * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
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 along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include <gtkmm/frame.h>
22 #include "pbd/unwind.h"
24 #include "evoral/midi_events.h"
25 #include "evoral/PatchChange.h"
27 #include "midi++/midnam_patch.h"
29 #include "ardour/instrument_info.h"
30 #include "ardour/midi_track.h"
31 #include "ardour/plugin_insert.h"
33 #include "gtkmm2ext/menu_elems.h"
34 #include "gtkmm2ext/utils.h"
35 #include "widgets/tooltips.h"
37 #include "gui_thread.h"
38 #include "patch_change_widget.h"
39 #include "ui_config.h"
44 using namespace ARDOUR;
46 PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
48 , _bank_msb_spin (*manage (new Adjustment (0, 0, 127, 1, 16)))
49 , _bank_lsb_spin (*manage (new Adjustment (0, 0, 127, 1, 16)))
50 , _program_table (/*rows*/ 16, /*cols*/ 8, true)
52 , _ignore_spin_btn_signals (false)
53 , _no_notifications (false)
54 , _info (r->instrument_info ())
55 , _audition_enable (_("Audition on Change"), ArdourWidgets::ArdourButton::led_default_elements)
56 , _audition_start_spin (*manage (new Adjustment (48, 0, 127, 1, 16)))
57 , _audition_end_spin (*manage (new Adjustment (60, 0, 127, 1, 16)))
58 , _audition_velocity (*manage (new Adjustment (100, 1, 127, 1, 16)))
59 , _audition_note_on (false)
62 box = manage (new HBox ());
63 box->set_border_width (2);
65 box->pack_start (*manage (new Label (_("Channel:"))), false, false);
66 box->pack_start (_channel_select, false, false);
67 box->pack_start (*manage (new Label (_("Bank:"))), false, false);
68 box->pack_start (_bank_select, true, true);
69 box->pack_start (*manage (new Label (_("MSB:"))), false, false);
70 box->pack_start (_bank_msb_spin, false, false);
71 box->pack_start (*manage (new Label (_("LSB:"))), false, false);
72 box->pack_start (_bank_lsb_spin, false, false);
74 pack_start (*box, false, false);
76 _program_table.set_spacings (1);
77 pack_start (_program_table, true, true);
79 if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
80 pack_start ( *manage (new Label (_("Note: Patch Selection is volatile (only Midi-Tracks retain bank/patch selection)."))), false, false);
83 box = manage (new HBox ());
85 box->pack_start (_audition_enable, false, false);
86 box->pack_start (*manage (new Label (_("Start Note:"))), false, false);
87 box->pack_start (_audition_start_spin, false, false);
88 box->pack_start (*manage (new Label (_("End Note:"))), false, false);
89 box->pack_start (_audition_end_spin, false, false);
90 box->pack_start (*manage (new Label (_("Velocity:"))), false, false);
91 box->pack_start (_audition_velocity, false, false);
93 Box* box2 = manage (new HBox ());
94 box2->pack_start (*box, true, false);
95 box2->set_border_width (2);
96 pack_start (*box2, false, false);
98 for (uint8_t pgm = 0; pgm < 128; ++pgm) {
99 _program_btn[pgm].set_text_ellipsize (Pango::ELLIPSIZE_END);
100 _program_btn[pgm].set_layout_ellipsize_width (PANGO_SCALE * 112 * UIConfiguration::instance ().get_ui_scale ());
103 _program_table.attach (_program_btn[pgm], col, col + 1, row, row + 1);
104 _program_btn[pgm].signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_program), pgm));
107 for (uint32_t chn = 0; chn < 16; ++chn) {
108 using namespace Menu_Helpers;
109 using namespace Gtkmm2ext;
111 snprintf (buf, sizeof(buf), "%d", chn + 1);
112 _channel_select.AddMenuElem (MenuElemNoMnemonic (buf, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_channel), chn)));
115 _piano.set_monophonic (true);
116 _piano.NoteOn.connect (sigc::mem_fun (*this, &PatchChangeWidget::_note_on_event_handler));
117 _piano.NoteOff.connect (sigc::mem_fun (*this, &PatchChangeWidget::note_off_event_handler));
119 _piano.set_flags(Gtk::CAN_FOCUS);
120 pack_start (_piano, false, false);
122 _audition_start_spin.set_sensitive (false);
123 _audition_end_spin.set_sensitive (false);
125 _audition_enable.signal_clicked.connect (sigc::mem_fun (*this, &PatchChangeWidget::audition_toggle));
126 _audition_start_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), false));
127 _audition_end_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), true));
128 _bank_msb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
129 _bank_lsb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
131 _info.Changed.connect (_info_changed_connection, invalidator (*this),
132 boost::bind (&PatchChangeWidget::instrument_info_changed, this), gui_context());
134 if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
135 _route->processors_changed.connect (_route_connection, invalidator (*this),
136 boost::bind (&PatchChangeWidget::processors_changed, this), gui_context());
137 processors_changed ();
144 PatchChangeWidget::~PatchChangeWidget ()
150 PatchChangeWidget::refresh ()
158 PatchChangeWidget::on_show ()
160 Gtk::VBox::on_show ();
167 PatchChangeWidget::on_hide ()
169 Gtk::VBox::on_hide ();
170 _ac_connections.drop_connections ();
175 PatchChangeWidget::select_channel (uint8_t chn)
180 if (_channel == chn) {
186 _channel_select.set_text (string_compose ("%1", (int)(chn + 1)));
188 _no_notifications = false;
190 _ac_connections.drop_connections ();
192 if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
193 boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
194 boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
195 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
197 bank_msb->Changed.connect (_ac_connections, invalidator (*this),
198 boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
199 bank_lsb->Changed.connect (_ac_connections, invalidator (*this),
200 boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
201 program->Changed.connect (_ac_connections, invalidator (*this),
202 boost::bind (&PatchChangeWidget::program_changed, this), gui_context ());
203 } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
204 if (pi->plugin()->knows_bank_patch ()) {
205 pi->plugin ()->BankPatchChange.connect (_ac_connections, invalidator (*this),
206 boost::bind (&PatchChangeWidget::bankpatch_changed, this, _1), gui_context ());
208 _no_notifications = true;
209 // TODO add note: instrument does not report changes.
217 PatchChangeWidget::refill_banks ()
220 using namespace Menu_Helpers;
221 using namespace Gtkmm2ext;
223 _current_patch_bank.reset ();
224 _bank_select.clear_items ();
226 const int b = bank (_channel);
229 PBD::Unwinder<bool> uw (_ignore_spin_btn_signals, true);
230 _bank_msb_spin.set_value (b >> 7);
231 _bank_lsb_spin.set_value (b & 127);
234 boost::shared_ptr<MIDI::Name::ChannelNameSet> cns = _info.get_patches (_channel);
236 for (MIDI::Name::ChannelNameSet::PatchBanks::const_iterator i = cns->patch_banks().begin(); i != cns->patch_banks().end(); ++i) {
237 std::string n = (*i)->name ();
238 if ((*i)->number () == UINT16_MAX) {
241 _bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), (*i)->number ())));
242 if ((*i)->number () == b) {
243 _current_patch_bank = *i;
244 _bank_select.set_text (n);
249 if (!_current_patch_bank) {
250 std::string n = string_compose (_("Bank %1"), b);
251 _bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), b)));
252 _bank_select.set_text (n);
255 refill_program_list ();
259 PatchChangeWidget::refill_program_list ()
261 std::bitset<128> unset_notes;
264 if (_current_patch_bank) {
265 const MIDI::Name::PatchNameList& patches = _current_patch_bank->patch_name_list ();
266 for (MIDI::Name::PatchNameList::const_iterator i = patches.begin(); i != patches.end(); ++i) {
267 const std::string n = (*i)->name ();
268 MIDI::Name::PatchPrimaryKey const& key = (*i)->patch_primary_key ();
270 const uint8_t pgm = key.program();
271 _program_btn[pgm].set_text (n);
272 set_tooltip (_program_btn[pgm], string_compose (_("%1 (Pgm-%2)"),
273 Gtkmm2ext::markup_escape_text (n), (int)(pgm +1)));
274 unset_notes.reset (pgm);
278 for (uint8_t pgm = 0; pgm < 128; ++pgm) {
279 if (!unset_notes.test (pgm)) {
280 _program_btn[pgm].set_name (X_("patch change button"));
283 std::string n = string_compose (_("Pgm-%1"), (int)(pgm +1));
284 _program_btn[pgm].set_text (n);
285 _program_btn[pgm].set_name (X_("patch change dim button"));
286 set_tooltip (_program_btn[pgm], n);
292 /* ***** user GUI actions *****/
295 PatchChangeWidget::select_bank_spin ()
297 if (_ignore_spin_btn_signals) {
300 const uint32_t b = (_bank_msb_spin.get_value_as_int() << 7) + _bank_lsb_spin.get_value_as_int();
305 PatchChangeWidget::select_bank (uint32_t bank)
309 if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
310 boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_MSB_BANK), true);
311 boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_LSB_BANK), true);
313 bank_msb->set_value (bank >> 7, PBD::Controllable::NoGroup);
314 bank_lsb->set_value (bank & 127, PBD::Controllable::NoGroup);
315 } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
317 event[0] = (MIDI_CMD_CONTROL | _channel);
319 event[2] = bank >> 7;
320 pi->write_immediate_event (3, event);
323 event[2] = bank & 127;
324 pi->write_immediate_event (3, event);
327 select_program (program (_channel));
331 PatchChangeWidget::select_program (uint8_t pgm)
334 if (_no_notifications) {
342 if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
343 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, _channel), true);
344 program->set_value (pgm, PBD::Controllable::NoGroup);
345 } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
347 event[0] = (MIDI_CMD_PGM_CHANGE | _channel);
349 pi->write_immediate_event (2, event);
355 /* ***** callbacks, external changes *****/
358 PatchChangeWidget::bank_changed ()
364 PatchChangeWidget::bankpatch_changed (uint8_t chn)
366 if (chn == _channel) {
372 PatchChangeWidget::program_changed ()
374 uint8_t p = program (_channel);
375 for (uint8_t pgm = 0; pgm < 128; ++pgm) {
376 _program_btn[pgm].set_active (pgm == p);
381 PatchChangeWidget::instrument_info_changed ()
387 PatchChangeWidget::processors_changed ()
389 assert (!boost::dynamic_pointer_cast<MidiTrack> (_route));
390 if (_route->the_instrument ()) {
391 set_sensitive (true);
393 set_sensitive (false);
397 /* ***** play notes *****/
400 PatchChangeWidget::audition_toggle ()
402 _audition_enable.set_active (!_audition_enable.get_active ());
403 if (_audition_enable.get_active()) {
404 _audition_start_spin.set_sensitive (true);
405 _audition_end_spin.set_sensitive (true);
408 _audition_start_spin.set_sensitive (false);
409 _audition_end_spin.set_sensitive (false);
414 PatchChangeWidget::check_note_range (bool upper)
416 int s = _audition_start_spin.get_value_as_int ();
417 int e = _audition_end_spin.get_value_as_int ();
422 _audition_start_spin.set_value (e);
424 _audition_end_spin.set_value (s);
429 PatchChangeWidget::cancel_audition ()
431 _note_queue_connection.disconnect();
433 if (_audition_note_on) {
434 note_off_event_handler (_audition_note_num);
435 _piano.set_note_off (_audition_note_num);
440 PatchChangeWidget::audition ()
442 if (!boost::dynamic_pointer_cast<MidiTrack> (_route) && !boost::dynamic_pointer_cast<PluginInsert> (_route)) {
449 if (_note_queue_connection.connected ()) {
453 if (!_audition_enable.get_active ()) {
457 assert (!_audition_note_on);
458 _audition_note_num = _audition_start_spin.get_value_as_int ();
460 _note_queue_connection = Glib::signal_timeout().connect (sigc::bind (sigc::mem_fun (&PatchChangeWidget::audition_next), this), 250);
464 PatchChangeWidget::audition_next ()
466 if (_audition_note_on) {
467 note_off_event_handler (_audition_note_num);
468 _piano.set_note_off (_audition_note_num);
469 return ++_audition_note_num <= _audition_end_spin.get_value_as_int() && _audition_enable.get_active ();
471 note_on_event_handler (_audition_note_num, true);
472 _piano.set_note_on (_audition_note_num);
478 PatchChangeWidget::_note_on_event_handler (int note, int)
480 note_on_event_handler(note, false);
484 PatchChangeWidget::note_on_event_handler (int note, bool for_audition)
488 _piano.grab_focus ();
491 event[0] = (MIDI_CMD_NOTE_ON | _channel);
493 event[2] = _audition_velocity.get_value_as_int ();
494 _audition_note_on = true;
495 _audition_note_num = note;
497 if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
498 mt->write_immediate_event (3, event);
499 } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
500 pi->write_immediate_event (3, event);
505 PatchChangeWidget::note_off_event_handler (int note)
508 event[0] = (MIDI_CMD_NOTE_OFF | _channel);
511 _audition_note_on = false;
513 if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
514 mt->write_immediate_event (3, event);
515 } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
516 pi->write_immediate_event (3, event);
520 /* ***** query info *****/
523 PatchChangeWidget::bank (uint8_t chn) const
525 if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
526 boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
527 boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
529 return ((int)bank_msb->get_value () << 7) + (int)bank_lsb->get_value();
530 } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
531 uint32_t bankpatch = pi->plugin()->bank_patch (chn);
532 if (bankpatch != UINT32_MAX) {
533 return bankpatch >> 7;
540 PatchChangeWidget::program (uint8_t chn) const
542 if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
543 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
544 return program->get_value();
545 } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
546 uint32_t bankpatch = pi->plugin()->bank_patch (chn);
547 if (bankpatch != UINT32_MAX) {
548 return bankpatch & 127;
554 /* ***************************************************************************/
556 PatchChangeGridDialog::PatchChangeGridDialog (boost::shared_ptr<ARDOUR::Route> r)
557 : ArdourDialog (string_compose (_("Select Patch for \"%1\""), r->name()), false, false)
560 r->PropertyChanged.connect (_route_connection, invalidator (*this), boost::bind (&PatchChangeGridDialog::route_property_changed, this, _1, boost::weak_ptr<Route>(r)), gui_context());
566 PatchChangeGridDialog::route_property_changed (const PBD::PropertyChange& what_changed, boost::weak_ptr<Route> wr)
568 boost::shared_ptr<ARDOUR::Route> r = wr.lock ();
569 if (r && what_changed.contains (ARDOUR::Properties::name)) {
570 set_title (string_compose (_("Select Patch for \"%1\"'"), r->name()));