Fix thinkos in cubasish theme
[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.h"
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 {
61         Box* box;
62         box = manage (new HBox ());
63         box->set_border_width (2);
64         box->set_spacing (4);
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);
73
74         pack_start (*box, false, false);
75
76         _program_table.set_spacings (1);
77         pack_start (_program_table, true, true);
78
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);
81         }
82
83         box = manage (new HBox ());
84         box->set_spacing (4);
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);
92
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);
97
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 ());
101                 int row = pgm % 16;
102                 int col = pgm / 16;
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));
105         }
106
107         for (uint32_t chn = 0; chn < 16; ++chn) {
108                 using namespace Menu_Helpers;
109                 using namespace Gtkmm2ext;
110                 char buf[8];
111                 snprintf (buf, sizeof(buf), "%d", chn + 1);
112                 _channel_select.AddMenuElem (MenuElemNoMnemonic (buf, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_channel), chn)));
113         }
114
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));
118
119         _piano.set_flags(Gtk::CAN_FOCUS);
120         pack_start (_piano, false, false);
121
122         _audition_start_spin.set_sensitive (false);
123         _audition_end_spin.set_sensitive (false);
124
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));
130
131         _info.Changed.connect (_info_changed_connection, invalidator (*this),
132                         boost::bind (&PatchChangeWidget::instrument_info_changed, this), gui_context());
133
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 ();
138         }
139
140         set_spacing (4);
141         show_all ();
142 }
143
144 PatchChangeWidget::~PatchChangeWidget ()
145 {
146         cancel_audition ();
147 }
148
149 void
150 PatchChangeWidget::refresh ()
151 {
152         if (is_visible ()) {
153                 on_show ();
154         }
155 }
156
157 void
158 PatchChangeWidget::on_show ()
159 {
160         Gtk::VBox::on_show ();
161         cancel_audition ();
162         _channel = -1;
163         select_channel (0);
164 }
165
166 void
167 PatchChangeWidget::on_hide ()
168 {
169         Gtk::VBox::on_hide ();
170         _ac_connections.drop_connections ();
171         cancel_audition ();
172 }
173
174 void
175 PatchChangeWidget::select_channel (uint8_t chn)
176 {
177         assert (_route);
178         assert (chn < 16);
179
180         if (_channel == chn) {
181                 return;
182         }
183
184         cancel_audition ();
185
186         _channel_select.set_text (string_compose ("%1", (int)(chn + 1)));
187         _channel = chn;
188         _no_notifications = false;
189
190         _ac_connections.drop_connections ();
191
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);
196
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 ());
207                 } else {
208                         _no_notifications = true;
209                         // TODO add note: instrument does not report changes.
210                 }
211         }
212
213         refill_banks ();
214 }
215
216 void
217 PatchChangeWidget::refill_banks ()
218 {
219         cancel_audition ();
220         using namespace Menu_Helpers;
221         using namespace Gtkmm2ext;
222
223         _current_patch_bank.reset ();
224         _bank_select.clear_items ();
225
226         const int b = bank (_channel);
227
228         {
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);
232         }
233
234         boost::shared_ptr<MIDI::Name::ChannelNameSet> cns = _info.get_patches (_channel);
235         if (cns) {
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) {
239                                 continue;
240                         }
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);
245                         }
246                 }
247         }
248
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);
253         }
254
255         refill_program_list ();
256 }
257
258 void
259 PatchChangeWidget::refill_program_list ()
260 {
261         std::bitset<128> unset_notes;
262         unset_notes.set ();
263
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 ();
269
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);
275                 }
276         }
277
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"));
281                         continue;
282                 }
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);
287         }
288
289         program_changed ();
290 }
291
292 /* ***** user GUI actions *****/
293
294 void
295 PatchChangeWidget::select_bank_spin ()
296 {
297         if (_ignore_spin_btn_signals) {
298                 return;
299         }
300         const uint32_t b = (_bank_msb_spin.get_value_as_int() << 7) + _bank_lsb_spin.get_value_as_int();
301         select_bank (b);
302 }
303
304 void
305 PatchChangeWidget::select_bank (uint32_t bank)
306 {
307         cancel_audition ();
308
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);
312
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())) {
316                 uint8_t event[3];
317                 event[0] = (MIDI_CMD_CONTROL | _channel);
318                 event[1] = 0x00;
319                 event[2] = bank >> 7;
320                 pi->write_immediate_event (3, event);
321
322                 event[1] = 0x20;
323                 event[2] = bank & 127;
324                 pi->write_immediate_event (3, event);
325         }
326
327         select_program (program (_channel));
328 }
329
330 void
331 PatchChangeWidget::select_program (uint8_t pgm)
332 {
333         cancel_audition ();
334         if (_no_notifications) {
335                 program_changed ();
336         }
337
338         if (pgm > 127) {
339                 return;
340         }
341
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())) {
346                 uint8_t event[2];
347                 event[0] = (MIDI_CMD_PGM_CHANGE | _channel);
348                 event[1] = pgm;
349                 pi->write_immediate_event (2, event);
350         }
351
352         audition ();
353 }
354
355 /* ***** callbacks, external changes *****/
356
357 void
358 PatchChangeWidget::bank_changed ()
359 {
360         refill_banks ();
361 }
362
363 void
364 PatchChangeWidget::bankpatch_changed (uint8_t chn)
365 {
366         if (chn == _channel) {
367                 refill_banks ();
368         }
369 }
370
371 void
372 PatchChangeWidget::program_changed ()
373 {
374         uint8_t p = program (_channel);
375         for (uint8_t pgm = 0; pgm < 128; ++pgm) {
376                 _program_btn[pgm].set_active (pgm == p);
377         }
378 }
379
380 void
381 PatchChangeWidget::instrument_info_changed ()
382 {
383         refill_banks ();
384 }
385
386 void
387 PatchChangeWidget::processors_changed ()
388 {
389         assert (!boost::dynamic_pointer_cast<MidiTrack> (_route));
390         if (_route->the_instrument ()) {
391                 set_sensitive (true);
392         } else {
393                 set_sensitive (false);
394         }
395 }
396
397 /* ***** play notes *****/
398
399 void
400 PatchChangeWidget::audition_toggle ()
401 {
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);
406         } else {
407                 cancel_audition ();
408                 _audition_start_spin.set_sensitive (false);
409                 _audition_end_spin.set_sensitive (false);
410         }
411 }
412
413 void
414 PatchChangeWidget::check_note_range (bool upper)
415 {
416         int s = _audition_start_spin.get_value_as_int ();
417         int e = _audition_end_spin.get_value_as_int ();
418         if (s <= e) {
419                 return;
420         }
421         if (upper) {
422                 _audition_start_spin.set_value (e);
423         } else {
424                 _audition_end_spin.set_value (s);
425         }
426 }
427
428 void
429 PatchChangeWidget::cancel_audition ()
430 {
431         _note_queue_connection.disconnect();
432
433         if (_audition_note_on) {
434                 note_off_event_handler (_audition_note_num);
435                 _piano.set_note_off (_audition_note_num);
436         }
437 }
438
439 void
440 PatchChangeWidget::audition ()
441 {
442         if (!boost::dynamic_pointer_cast<MidiTrack> (_route) && !boost::dynamic_pointer_cast<PluginInsert> (_route)) {
443                 return;
444         }
445         if (_channel > 16) {
446                 return;
447         }
448
449         if (_note_queue_connection.connected ()) {
450                 cancel_audition ();
451         }
452
453         if (!_audition_enable.get_active ()) {
454                 return;
455         }
456
457         assert (!_audition_note_on);
458         _audition_note_num = _audition_start_spin.get_value_as_int ();
459
460         _note_queue_connection = Glib::signal_timeout().connect (sigc::bind (sigc::mem_fun (&PatchChangeWidget::audition_next), this), 250);
461 }
462
463 bool
464 PatchChangeWidget::audition_next ()
465 {
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 ();
470         } else {
471                 note_on_event_handler (_audition_note_num, true);
472                 _piano.set_note_on (_audition_note_num);
473                 return true;
474         }
475 }
476
477 void
478 PatchChangeWidget::_note_on_event_handler (int note, int)
479 {
480         note_on_event_handler(note, false);
481 }
482
483 void
484 PatchChangeWidget::note_on_event_handler (int note, bool for_audition)
485 {
486         if (!for_audition) {
487                 cancel_audition ();
488                 _piano.grab_focus ();
489         }
490         uint8_t event[3];
491         event[0] = (MIDI_CMD_NOTE_ON | _channel);
492         event[1] = note;
493         event[2] = _audition_velocity.get_value_as_int ();
494         _audition_note_on = true;
495         _audition_note_num = note;
496
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);
501         }
502 }
503
504 void
505 PatchChangeWidget::note_off_event_handler (int note)
506 {
507         uint8_t event[3];
508         event[0] = (MIDI_CMD_NOTE_OFF | _channel);
509         event[1] = note;
510         event[2] = 0;
511         _audition_note_on = false;
512
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);
517         }
518 }
519
520 /* ***** query info *****/
521
522 int
523 PatchChangeWidget::bank (uint8_t chn) const
524 {
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);
528
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;
534                 }
535         }
536         return 0;
537 }
538
539 uint8_t
540 PatchChangeWidget::program (uint8_t chn) const
541 {
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;
549                 }
550         }
551         return 255;
552 }
553
554 /* ***************************************************************************/
555
556 PatchChangeGridDialog::PatchChangeGridDialog (boost::shared_ptr<ARDOUR::Route> r)
557         : ArdourDialog (string_compose (_("Select Patch for \"%1\""), r->name()), false, false)
558         , w (r)
559 {
560         r->PropertyChanged.connect (_route_connection, invalidator (*this), boost::bind (&PatchChangeGridDialog::route_property_changed, this, _1, boost::weak_ptr<Route>(r)), gui_context());
561         get_vbox()->add (w);
562         w.show ();
563 }
564
565 void
566 PatchChangeGridDialog::route_property_changed (const PBD::PropertyChange& what_changed, boost::weak_ptr<Route> wr)
567 {
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()));
571         }
572 }