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