Fix processor box for no crash on click
[ardour.git] / gtk2_ardour / patch_change_widget.cc
1 /*
2  * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 #include <bitset>
20 #include <gtkmm/frame.h>
21
22 #include "pbd/unwind.h"
23
24 #include "evoral/midi_events.h"
25 #include "evoral/PatchChange.hpp"
26
27 #include "midi++/midnam_patch.h"
28
29 #include "ardour/instrument_info.h"
30 #include "ardour/midi_track.h"
31 #include "ardour/plugin_insert.h"
32
33 #include "gtkmm2ext/menu_elems.h"
34 #include "gtkmm2ext/utils.h"
35 #include "widgets/tooltips.h"
36
37 #include "gui_thread.h"
38 #include "patch_change_widget.h"
39 #include "ui_config.h"
40
41 #include "pbd/i18n.h"
42
43 using namespace Gtk;
44 using namespace ARDOUR;
45
46 PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
47         : _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)
51         , _channel (-1)
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)
60         , _piano ((PianoKeyboard*)piano_keyboard_new())
61         , _pianomm (Glib::wrap((GtkWidget*)_piano))
62 {
63         Box* box;
64         box = manage (new HBox ());
65         box->set_border_width (2);
66         box->set_spacing (4);
67         box->pack_start (*manage (new Label (_("Channel:"))), false, false);
68         box->pack_start (_channel_select, false, false);
69         box->pack_start (*manage (new Label (_("Bank:"))), false, false);
70         box->pack_start (_bank_select, true, true);
71         box->pack_start (*manage (new Label (_("MSB:"))), false, false);
72         box->pack_start (_bank_msb_spin, false, false);
73         box->pack_start (*manage (new Label (_("LSB:"))), false, false);
74         box->pack_start (_bank_lsb_spin, false, false);
75
76         pack_start (*box, false, false);
77
78         _program_table.set_spacings (1);
79         pack_start (_program_table, true, true);
80
81         if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
82                 pack_start ( *manage (new Label (_("Note: Patch Selection is volatile (only Midi-Tracks retain bank/patch selection)."))), false, false);
83         }
84
85         box = manage (new HBox ());
86         box->set_spacing (4);
87         box->pack_start (_audition_enable, false, false);
88         box->pack_start (*manage (new Label (_("Start Note:"))), false, false);
89         box->pack_start (_audition_start_spin, false, false);
90         box->pack_start (*manage (new Label (_("End Note:"))), false, false);
91         box->pack_start (_audition_end_spin, false, false);
92         box->pack_start (*manage (new Label (_("Velocity:"))), false, false);
93         box->pack_start (_audition_velocity, false, false);
94
95         Box* box2 = manage (new HBox ());
96         box2->pack_start (*box, true, false);
97         box2->set_border_width (2);
98         pack_start (*box2, false, false);
99
100         for (uint8_t pgm = 0; pgm < 128; ++pgm) {
101                 _program_btn[pgm].set_text_ellipsize (Pango::ELLIPSIZE_END);
102                 _program_btn[pgm].set_layout_ellipsize_width (PANGO_SCALE * 112 * UIConfiguration::instance ().get_ui_scale ());
103                 int row = pgm % 16;
104                 int col = pgm / 16;
105                 _program_table.attach (_program_btn[pgm], col, col + 1, row, row + 1);
106                 _program_btn[pgm].signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_program), pgm));
107         }
108
109         for (uint32_t chn = 0; chn < 16; ++chn) {
110                 using namespace Menu_Helpers;
111                 using namespace Gtkmm2ext;
112                 char buf[8];
113                 snprintf (buf, sizeof(buf), "%d", chn + 1);
114                 _channel_select.AddMenuElem (MenuElemNoMnemonic (buf, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_channel), chn)));
115         }
116
117         piano_keyboard_set_monophonic (_piano, TRUE);
118         g_signal_connect (G_OBJECT (_piano), "note-on", G_CALLBACK (PatchChangeWidget::_note_on_event_handler), this);
119         g_signal_connect (G_OBJECT (_piano), "note-off", G_CALLBACK (PatchChangeWidget::_note_off_event_handler), this);
120         _pianomm->set_flags(Gtk::CAN_FOCUS);
121         pack_start (*_pianomm, false, false);
122
123         _audition_start_spin.set_sensitive (false);
124         _audition_end_spin.set_sensitive (false);
125
126         _audition_enable.signal_clicked.connect (sigc::mem_fun (*this, &PatchChangeWidget::audition_toggle));
127         _audition_start_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), false));
128         _audition_end_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), true));
129         _bank_msb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
130         _bank_lsb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
131
132         _info.Changed.connect (_info_changed_connection, invalidator (*this),
133                         boost::bind (&PatchChangeWidget::instrument_info_changed, this), gui_context());
134
135         if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
136                 _route->processors_changed.connect (_route_connection, invalidator (*this),
137                                 boost::bind (&PatchChangeWidget::processors_changed, this), gui_context());
138                 processors_changed ();
139         }
140
141         set_spacing (4);
142         show_all ();
143 }
144
145 PatchChangeWidget::~PatchChangeWidget ()
146 {
147         cancel_audition ();
148         delete _pianomm;
149 }
150
151 void
152 PatchChangeWidget::refresh ()
153 {
154         if (is_visible ()) {
155                 on_show ();
156         }
157 }
158
159 void
160 PatchChangeWidget::on_show ()
161 {
162         Gtk::VBox::on_show ();
163         cancel_audition ();
164         _channel = -1;
165         select_channel (0);
166 }
167
168 void
169 PatchChangeWidget::on_hide ()
170 {
171         Gtk::VBox::on_hide ();
172         _ac_connections.drop_connections ();
173         cancel_audition ();
174 }
175
176 void
177 PatchChangeWidget::select_channel (uint8_t chn)
178 {
179         assert (_route);
180         assert (chn < 16);
181
182         if (_channel == chn) {
183                 return;
184         }
185
186         cancel_audition ();
187
188         _channel_select.set_text (string_compose ("%1", (int)(chn + 1)));
189         _channel = chn;
190         _no_notifications = false;
191
192         _ac_connections.drop_connections ();
193
194         if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
195                 boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
196                 boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
197                 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
198
199                 bank_msb->Changed.connect (_ac_connections, invalidator (*this),
200                                 boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
201                 bank_lsb->Changed.connect (_ac_connections, invalidator (*this),
202                                 boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
203                 program->Changed.connect (_ac_connections, invalidator (*this),
204                                 boost::bind (&PatchChangeWidget::program_changed, this), gui_context ());
205         } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
206                 if (pi->plugin()->knows_bank_patch ()) {
207                         pi->plugin ()->BankPatchChange.connect (_ac_connections, invalidator (*this),
208                                         boost::bind (&PatchChangeWidget::bankpatch_changed, this, _1), gui_context ());
209                 } else {
210                         _no_notifications = true;
211                         // TODO add note: instrument does not report changes.
212                 }
213         }
214
215         refill_banks ();
216 }
217
218 void
219 PatchChangeWidget::refill_banks ()
220 {
221         cancel_audition ();
222         using namespace Menu_Helpers;
223         using namespace Gtkmm2ext;
224
225         _current_patch_bank.reset ();
226         _bank_select.clear_items ();
227
228         const int b = bank (_channel);
229
230         {
231                 PBD::Unwinder<bool> uw (_ignore_spin_btn_signals, true);
232                 _bank_msb_spin.set_value (b >> 7);
233                 _bank_lsb_spin.set_value (b & 127);
234         }
235
236         boost::shared_ptr<MIDI::Name::ChannelNameSet> cns = _info.get_patches (_channel);
237         if (cns) {
238                 for (MIDI::Name::ChannelNameSet::PatchBanks::const_iterator i = cns->patch_banks().begin(); i != cns->patch_banks().end(); ++i) {
239                         std::string n = (*i)->name ();
240                         if ((*i)->number () == UINT16_MAX) {
241                                 continue;
242                         }
243                         _bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), (*i)->number ())));
244                         if ((*i)->number () == b) {
245                                 _current_patch_bank = *i;
246                                 _bank_select.set_text (n);
247                         }
248                 }
249         }
250
251         if (!_current_patch_bank) {
252                 std::string n = string_compose (_("Bank %1"), b);
253                 _bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), b)));
254                 _bank_select.set_text (n);
255         }
256
257         refill_program_list ();
258 }
259
260 void
261 PatchChangeWidget::refill_program_list ()
262 {
263         std::bitset<128> unset_notes;
264         unset_notes.set ();
265
266         if (_current_patch_bank) {
267                 const MIDI::Name::PatchNameList& patches = _current_patch_bank->patch_name_list ();
268                 for (MIDI::Name::PatchNameList::const_iterator i = patches.begin(); i != patches.end(); ++i) {
269                         const std::string n = (*i)->name ();
270                         MIDI::Name::PatchPrimaryKey const& key = (*i)->patch_primary_key ();
271
272                         const uint8_t pgm = key.program();
273                         _program_btn[pgm].set_text (n);
274                         set_tooltip (_program_btn[pgm], string_compose (_("%1 (Pgm-%2)"),
275                                                 Gtkmm2ext::markup_escape_text (n), (int)(pgm +1)));
276                         unset_notes.reset (pgm);
277                 }
278         }
279
280         for (uint8_t pgm = 0; pgm < 128; ++pgm) {
281                 if (!unset_notes.test (pgm)) {
282                         _program_btn[pgm].set_name (X_("patch change button"));
283                         continue;
284                 }
285                 std::string n = string_compose (_("Pgm-%1"), (int)(pgm +1));
286                 _program_btn[pgm].set_text (n);
287                 _program_btn[pgm].set_name (X_("patch change dim button"));
288                 set_tooltip (_program_btn[pgm], n);
289         }
290
291         program_changed ();
292 }
293
294 /* ***** user GUI actions *****/
295
296 void
297 PatchChangeWidget::select_bank_spin ()
298 {
299         if (_ignore_spin_btn_signals) {
300                 return;
301         }
302         const uint32_t b = (_bank_msb_spin.get_value_as_int() << 7) + _bank_lsb_spin.get_value_as_int();
303         select_bank (b);
304 }
305
306 void
307 PatchChangeWidget::select_bank (uint32_t bank)
308 {
309         cancel_audition ();
310
311         if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
312                 boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_MSB_BANK), true);
313                 boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_LSB_BANK), true);
314
315                 bank_msb->set_value (bank >> 7, PBD::Controllable::NoGroup);
316                 bank_lsb->set_value (bank & 127, PBD::Controllable::NoGroup);
317         } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
318                 uint8_t event[3];
319                 event[0] = (MIDI_CMD_CONTROL | _channel);
320                 event[1] = 0x00;
321                 event[2] = bank >> 7;
322                 pi->write_immediate_event (3, event);
323
324                 event[1] = 0x20;
325                 event[2] = bank & 127;
326                 pi->write_immediate_event (3, event);
327         }
328
329         select_program (program (_channel));
330 }
331
332 void
333 PatchChangeWidget::select_program (uint8_t pgm)
334 {
335         cancel_audition ();
336         if (_no_notifications) {
337                 program_changed ();
338         }
339
340         if (pgm > 127) {
341                 return;
342         }
343
344         if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
345                 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, _channel), true);
346                 program->set_value (pgm, PBD::Controllable::NoGroup);
347         } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
348                 uint8_t event[2];
349                 event[0] = (MIDI_CMD_PGM_CHANGE | _channel);
350                 event[1] = pgm;
351                 pi->write_immediate_event (2, event);
352         }
353
354         audition ();
355 }
356
357 /* ***** callbacks, external changes *****/
358
359 void
360 PatchChangeWidget::bank_changed ()
361 {
362         refill_banks ();
363 }
364
365 void
366 PatchChangeWidget::bankpatch_changed (uint8_t chn)
367 {
368         if (chn == _channel) {
369                 refill_banks ();
370         }
371 }
372
373 void
374 PatchChangeWidget::program_changed ()
375 {
376         uint8_t p = program (_channel);
377         for (uint8_t pgm = 0; pgm < 128; ++pgm) {
378                 _program_btn[pgm].set_active (pgm == p);
379         }
380 }
381
382 void
383 PatchChangeWidget::instrument_info_changed ()
384 {
385         refill_banks ();
386 }
387
388 void
389 PatchChangeWidget::processors_changed ()
390 {
391         assert (!boost::dynamic_pointer_cast<MidiTrack> (_route));
392         if (_route->the_instrument ()) {
393                 set_sensitive (true);
394         } else {
395                 set_sensitive (false);
396         }
397 }
398
399 /* ***** play notes *****/
400
401 void
402 PatchChangeWidget::audition_toggle ()
403 {
404         _audition_enable.set_active (!_audition_enable.get_active ());
405         if (_audition_enable.get_active()) {
406                 _audition_start_spin.set_sensitive (true);
407                 _audition_end_spin.set_sensitive (true);
408         } else {
409                 cancel_audition ();
410                 _audition_start_spin.set_sensitive (false);
411                 _audition_end_spin.set_sensitive (false);
412         }
413 }
414
415 void
416 PatchChangeWidget::check_note_range (bool upper)
417 {
418         int s = _audition_start_spin.get_value_as_int ();
419         int e = _audition_end_spin.get_value_as_int ();
420         if (s <= e) {
421                 return;
422         }
423         if (upper) {
424                 _audition_start_spin.set_value (e);
425         } else {
426                 _audition_end_spin.set_value (s);
427         }
428 }
429
430 void
431 PatchChangeWidget::cancel_audition ()
432 {
433         _note_queue_connection.disconnect();
434
435         if (_audition_note_on) {
436                 note_off_event_handler (_audition_note_num);
437                 piano_keyboard_set_note_off (_piano, _audition_note_num);
438         }
439 }
440
441 void
442 PatchChangeWidget::audition ()
443 {
444         if (!boost::dynamic_pointer_cast<MidiTrack> (_route) && !boost::dynamic_pointer_cast<PluginInsert> (_route)) {
445                 return;
446         }
447         if (_channel > 16) {
448                 return;
449         }
450
451         if (_note_queue_connection.connected ()) {
452                 cancel_audition ();
453         }
454
455         if (!_audition_enable.get_active ()) {
456                 return;
457         }
458
459         assert (!_audition_note_on);
460         _audition_note_num = _audition_start_spin.get_value_as_int ();
461
462         _note_queue_connection = Glib::signal_timeout().connect (sigc::bind (sigc::mem_fun (&PatchChangeWidget::audition_next), this), 250);
463 }
464
465 bool
466 PatchChangeWidget::audition_next ()
467 {
468         if (_audition_note_on) {
469                 note_off_event_handler (_audition_note_num);
470                 piano_keyboard_set_note_off (_piano, _audition_note_num);
471                 return ++_audition_note_num <= _audition_end_spin.get_value_as_int() && _audition_enable.get_active ();
472         } else {
473                 note_on_event_handler (_audition_note_num, true);
474                 piano_keyboard_set_note_on (_piano, _audition_note_num);
475                 return true;
476         }
477 }
478
479 void
480 PatchChangeWidget::_note_on_event_handler(GtkWidget*, int note, gpointer arg)
481 {
482         ((PatchChangeWidget*)arg)->note_on_event_handler(note, false);
483 }
484
485 void
486 PatchChangeWidget::_note_off_event_handler(GtkWidget*, int note, gpointer arg)
487 {
488         ((PatchChangeWidget*)arg)->note_off_event_handler(note);
489 }
490
491 void
492 PatchChangeWidget::note_on_event_handler (int note, bool for_audition)
493 {
494         if (!for_audition) {
495                 cancel_audition ();
496                 _pianomm->grab_focus ();
497         }
498         uint8_t event[3];
499         event[0] = (MIDI_CMD_NOTE_ON | _channel);
500         event[1] = note;
501         event[2] = _audition_velocity.get_value_as_int ();
502         _audition_note_on = true;
503         _audition_note_num = note;
504
505         if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
506                 mt->write_immediate_event (3, event);
507         } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
508                 pi->write_immediate_event (3, event);
509         }
510 }
511
512 void
513 PatchChangeWidget::note_off_event_handler (int note)
514 {
515         uint8_t event[3];
516         event[0] = (MIDI_CMD_NOTE_OFF | _channel);
517         event[1] = note;
518         event[2] = 0;
519         _audition_note_on = false;
520
521         if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
522                 mt->write_immediate_event (3, event);
523         } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
524                 pi->write_immediate_event (3, event);
525         }
526 }
527
528 /* ***** query info *****/
529
530 int
531 PatchChangeWidget::bank (uint8_t chn) const
532 {
533         if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
534                 boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
535                 boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
536
537                 return ((int)bank_msb->get_value () << 7) + (int)bank_lsb->get_value();
538         } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
539                 uint32_t bankpatch = pi->plugin()->bank_patch (chn);
540                 if (bankpatch != UINT32_MAX) {
541                         return bankpatch >> 7;
542                 }
543         }
544         return 0;
545 }
546
547 uint8_t
548 PatchChangeWidget::program (uint8_t chn) const
549 {
550         if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
551                 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
552                 return program->get_value();
553         } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
554                 uint32_t bankpatch = pi->plugin()->bank_patch (chn);
555                 if (bankpatch != UINT32_MAX) {
556                         return bankpatch & 127;
557                 }
558         }
559         return 255;
560 }
561
562 /* ***************************************************************************/
563
564 PatchChangeGridDialog::PatchChangeGridDialog (boost::shared_ptr<ARDOUR::Route> r)
565         : ArdourDialog (string_compose (_("Select Patch for \"%1\""), r->name()), false, false)
566         , w (r)
567 {
568         r->PropertyChanged.connect (_route_connection, invalidator (*this), boost::bind (&PatchChangeGridDialog::route_property_changed, this, _1, boost::weak_ptr<Route>(r)), gui_context());
569         get_vbox()->add (w);
570         w.show ();
571 }
572
573 void
574 PatchChangeGridDialog::route_property_changed (const PBD::PropertyChange& what_changed, boost::weak_ptr<Route> wr)
575 {
576         boost::shared_ptr<ARDOUR::Route> r = wr.lock ();
577         if (r && what_changed.contains (ARDOUR::Properties::name)) {
578                 set_title (string_compose (_("Select Patch for \"%1\"'"), r->name()));
579         }
580 }