PatchChange Dialog: update title & refresh midnam on change
[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
33 #include "gtkmm2ext/menu_elems.h"
34 #include "widgets/tooltips.h"
35
36 #include "gui_thread.h"
37 #include "patch_change_widget.h"
38 #include "ui_config.h"
39
40 #include "pbd/i18n.h"
41
42 using namespace Gtk;
43 using namespace ARDOUR;
44
45 PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
46         : _route (r)
47         , _bank_msb_spin (*manage (new Adjustment (0, 0, 127, 1, 16)))
48         , _bank_lsb_spin (*manage (new Adjustment (0, 0, 127, 1, 16)))
49         , _program_table (/*rows*/ 16, /*cols*/ 8, true)
50         , _channel (-1)
51         , _ignore_spin_btn_signals (false)
52         , _info (r->instrument_info ())
53         , _audition_enable (_("Audition on Change"), ArdourWidgets::ArdourButton::led_default_elements)
54         , _audition_start_spin (*manage (new Adjustment (48, 0, 127, 1, 16)))
55         , _audition_end_spin (*manage (new Adjustment (60, 0, 127, 1, 16)))
56         , _audition_velocity (*manage (new Adjustment (100, 1, 127, 1, 16)))
57         , _audition_note_on (false)
58         , _piano ((PianoKeyboard*)piano_keyboard_new())
59         , _pianomm (Glib::wrap((GtkWidget*)_piano))
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                 box = manage (new HBox ());
81                 box->set_spacing (4);
82                 box->pack_start (_audition_enable, false, false);
83                 box->pack_start (*manage (new Label (_("Start Note:"))), false, false);
84                 box->pack_start (_audition_start_spin, false, false);
85                 box->pack_start (*manage (new Label (_("End Note:"))), false, false);
86                 box->pack_start (_audition_end_spin, false, false);
87                 box->pack_start (*manage (new Label (_("Velocity:"))), false, false);
88                 box->pack_start (_audition_velocity, false, false);
89
90                 Box* box2 = manage (new HBox ());
91                 box2->pack_start (*box, true, false);
92                 box2->set_border_width (2);
93                 pack_start (*box2, false, false);
94         }
95
96         for (uint8_t pgm = 0; pgm < 128; ++pgm) {
97                 _program_btn[pgm].set_text_ellipsize (Pango::ELLIPSIZE_END);
98                 _program_btn[pgm].set_layout_ellipsize_width (PANGO_SCALE * 112 * UIConfiguration::instance ().get_ui_scale ());
99                 int row = pgm % 16;
100                 int col = pgm / 16;
101                 _program_table.attach (_program_btn[pgm], col, col + 1, row, row + 1);
102                 _program_btn[pgm].signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_program), pgm));
103         }
104
105         for (uint32_t chn = 0; chn < 16; ++chn) {
106                 using namespace Menu_Helpers;
107                 using namespace Gtkmm2ext;
108                 char buf[8];
109                 snprintf (buf, sizeof(buf), "%d", chn + 1);
110                 _channel_select.AddMenuElem (MenuElemNoMnemonic (buf, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_channel), chn)));
111         }
112
113         if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
114                 piano_keyboard_set_monophonic (_piano, TRUE);
115                 g_signal_connect (G_OBJECT (_piano), "note-on", G_CALLBACK (PatchChangeWidget::_note_on_event_handler), this);
116                 g_signal_connect (G_OBJECT (_piano), "note-off", G_CALLBACK (PatchChangeWidget::_note_off_event_handler), this);
117                 _pianomm->set_flags(Gtk::CAN_FOCUS);
118                 pack_start (*_pianomm, false, false);
119         }
120
121         _audition_start_spin.set_sensitive (false);
122         _audition_end_spin.set_sensitive (false);
123
124         _audition_enable.signal_clicked.connect (sigc::mem_fun (*this, &PatchChangeWidget::audition_toggle));
125         _audition_start_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), false));
126         _audition_end_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), true));
127         _bank_msb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
128         _bank_lsb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
129
130         _info.Changed.connect (_info_changed_connection, invalidator (*this),
131                         boost::bind (&PatchChangeWidget::instrument_info_changed, this), gui_context());
132
133         set_spacing (4);
134         show_all ();
135 }
136
137 PatchChangeWidget::~PatchChangeWidget ()
138 {
139         cancel_audition ();
140         delete _pianomm;
141 }
142
143 void
144 PatchChangeWidget::refresh ()
145 {
146         if (is_visible ()) {
147                 on_show ();
148         }
149 }
150
151 void
152 PatchChangeWidget::on_show ()
153 {
154         Gtk::VBox::on_show ();
155         cancel_audition ();
156         _channel = -1;
157         select_channel (0);
158 }
159
160 void
161 PatchChangeWidget::on_hide ()
162 {
163         Gtk::VBox::on_hide ();
164         _ac_connections.drop_connections ();
165         cancel_audition ();
166 }
167
168 void
169 PatchChangeWidget::select_channel (uint8_t chn)
170 {
171         assert (_route);
172         assert (chn < 16);
173
174         if (_channel == chn) {
175                 return;
176         }
177
178         cancel_audition ();
179
180         _channel_select.set_text (string_compose ("%1", (int)(chn + 1)));
181         _channel = chn;
182
183         _ac_connections.drop_connections ();
184
185         boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
186         boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
187         boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
188
189         bank_msb->Changed.connect (_ac_connections, invalidator (*this),
190                         boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
191         bank_lsb->Changed.connect (_ac_connections, invalidator (*this),
192                         boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
193         program->Changed.connect (_ac_connections, invalidator (*this),
194                         boost::bind (&PatchChangeWidget::program_changed, this), gui_context ());
195
196         refill_banks ();
197 }
198
199 void
200 PatchChangeWidget::refill_banks ()
201 {
202         cancel_audition ();
203         using namespace Menu_Helpers;
204         using namespace Gtkmm2ext;
205
206         _current_patch_bank.reset ();
207         _bank_select.clear_items ();
208
209         const int b = bank (_channel);
210
211         {
212                 PBD::Unwinder<bool> (_ignore_spin_btn_signals, true);
213                 _bank_msb_spin.set_value (b >> 7);
214                 _bank_lsb_spin.set_value (b & 127);
215         }
216
217         boost::shared_ptr<MIDI::Name::ChannelNameSet> cns = _info.get_patches (_channel);
218         if (cns) {
219                 for (MIDI::Name::ChannelNameSet::PatchBanks::const_iterator i = cns->patch_banks().begin(); i != cns->patch_banks().end(); ++i) {
220                         std::string n = (*i)->name ();
221                         _bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), (*i)->number ())));
222                         if ((*i)->number () == b) {
223                                 _current_patch_bank = *i;
224                                 _bank_select.set_text (n);
225                         }
226                 }
227         }
228
229         if (!_current_patch_bank) {
230                 std::string n = string_compose (_("Bank %1"), b);
231                 _bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), b)));
232                 _bank_select.set_text (n);
233         }
234
235         refill_program_list ();
236 }
237
238 void
239 PatchChangeWidget::refill_program_list ()
240 {
241         std::bitset<128> unset_notes;
242         unset_notes.set ();
243
244         if (_current_patch_bank) {
245                 const MIDI::Name::PatchNameList& patches = _current_patch_bank->patch_name_list ();
246                 for (MIDI::Name::PatchNameList::const_iterator i = patches.begin(); i != patches.end(); ++i) {
247                         const std::string n = (*i)->name ();
248                         MIDI::Name::PatchPrimaryKey const& key = (*i)->patch_primary_key ();
249
250                         const uint8_t pgm = key.program();
251                         _program_btn[pgm].set_text (n);
252                         set_tooltip (_program_btn[pgm], string_compose (_("%1 (Pgm-%2)"), n, (int)(pgm +1)));
253                         unset_notes.reset (pgm);
254                 }
255         }
256
257         for (uint8_t pgm = 0; pgm < 128; ++pgm) {
258                 if (!unset_notes.test (pgm)) {
259                         _program_btn[pgm].set_name (X_("patch change button"));
260                         continue;
261                 }
262                 std::string n = string_compose (_("Pgm-%1"), (int)(pgm +1));
263                 _program_btn[pgm].set_text (n);
264                 _program_btn[pgm].set_name (X_("patch change dim button"));
265                 set_tooltip (_program_btn[pgm], n);
266         }
267
268         program_changed ();
269 }
270
271 /* ***** user GUI actions *****/
272
273 void
274 PatchChangeWidget::select_bank_spin ()
275 {
276         if (_ignore_spin_btn_signals) {
277                 return;
278         }
279         const uint32_t b = (_bank_msb_spin.get_value_as_int() << 7) + _bank_lsb_spin.get_value_as_int();
280         select_bank (b);
281 }
282
283 void
284 PatchChangeWidget::select_bank (uint32_t bank)
285 {
286         cancel_audition ();
287
288         boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_MSB_BANK), true);
289         boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_LSB_BANK), true);
290
291         bank_msb->set_value (bank >> 7, PBD::Controllable::NoGroup);
292         bank_lsb->set_value (bank & 127, PBD::Controllable::NoGroup);
293         select_program (program (_channel));
294 }
295
296 void
297 PatchChangeWidget::select_program (uint8_t pgm)
298 {
299         cancel_audition ();
300
301         boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, _channel), true);
302         program->set_value (pgm, PBD::Controllable::NoGroup);
303
304         audition ();
305 }
306
307 /* ***** callbacks, external changes *****/
308
309 void
310 PatchChangeWidget::bank_changed ()
311 {
312         refill_banks ();
313 }
314
315 void
316 PatchChangeWidget::program_changed ()
317 {
318         uint8_t p = program (_channel);
319         for (uint8_t pgm = 0; pgm < 128; ++pgm) {
320                 _program_btn[pgm].set_active (pgm == p);
321         }
322 }
323
324 void
325 PatchChangeWidget::instrument_info_changed ()
326 {
327         refill_banks ();
328 }
329
330 /* ***** play notes *****/
331
332 void
333 PatchChangeWidget::audition_toggle ()
334 {
335         _audition_enable.set_active (!_audition_enable.get_active ());
336         if (_audition_enable.get_active()) {
337                 _audition_start_spin.set_sensitive (true);
338                 _audition_end_spin.set_sensitive (true);
339         } else {
340                 cancel_audition ();
341                 _audition_start_spin.set_sensitive (false);
342                 _audition_end_spin.set_sensitive (false);
343         }
344 }
345
346 void
347 PatchChangeWidget::check_note_range (bool upper)
348 {
349         int s = _audition_start_spin.get_value_as_int ();
350         int e = _audition_end_spin.get_value_as_int ();
351         if (s <= e) {
352                 return;
353         }
354         if (upper) {
355                 _audition_start_spin.set_value (e);
356         } else {
357                 _audition_end_spin.set_value (s);
358         }
359 }
360
361 void
362 PatchChangeWidget::cancel_audition ()
363 {
364         _note_queue_connection.disconnect();
365
366         if (_audition_note_on) {
367                 boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
368                 uint8_t event[3];
369
370                 event[0] = (MIDI_CMD_NOTE_OFF | _channel);
371                 event[1] = _audition_note_num;
372                 event[2] = 0;
373                 mt->write_immediate_event (3, event);
374                 piano_keyboard_set_note_off (_piano, _audition_note_num);
375         }
376         _audition_note_on = false;
377 }
378
379 void
380 PatchChangeWidget::audition ()
381 {
382         if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
383                 return;
384         }
385         if (_channel > 16) {
386                 return;
387         }
388
389         if (_note_queue_connection.connected ()) {
390                 cancel_audition ();
391         }
392
393         if (!_audition_enable.get_active ()) {
394                 return;
395         }
396
397         assert (!_audition_note_on);
398         _audition_note_num = _audition_start_spin.get_value_as_int ();
399
400         _note_queue_connection = Glib::signal_timeout().connect (sigc::bind (sigc::mem_fun (&PatchChangeWidget::audition_next), this), 250);
401 }
402
403 bool
404 PatchChangeWidget::audition_next ()
405 {
406         boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
407         uint8_t event[3];
408
409         if (_audition_note_on) {
410                 event[0] = (MIDI_CMD_NOTE_OFF | _channel);
411                 event[1] = _audition_note_num;
412                 event[2] = 0;
413                 mt->write_immediate_event (3, event);
414                 _audition_note_on = false;
415                 piano_keyboard_set_note_off (_piano, _audition_note_num);
416                 return ++_audition_note_num <= _audition_end_spin.get_value_as_int() && _audition_enable.get_active ();
417         } else {
418                 event[0] = (MIDI_CMD_NOTE_ON | _channel);
419                 event[1] = _audition_note_num;
420                 event[2] = _audition_velocity.get_value_as_int ();
421                 mt->write_immediate_event (3, event);
422                 _audition_note_on = true;
423                 piano_keyboard_set_note_on (_piano, _audition_note_num);
424                 return true;
425         }
426 }
427
428 void
429 PatchChangeWidget::_note_on_event_handler(GtkWidget*, int note, gpointer arg)
430 {
431         ((PatchChangeWidget*)arg)->note_on_event_handler(note);
432 }
433
434 void
435 PatchChangeWidget::_note_off_event_handler(GtkWidget*, int note, gpointer arg)
436 {
437         ((PatchChangeWidget*)arg)->note_off_event_handler(note);
438 }
439
440 void
441 PatchChangeWidget::note_on_event_handler (int note)
442 {
443         cancel_audition ();
444         _pianomm->grab_focus ();
445         boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
446         uint8_t event[3];
447         event[0] = (MIDI_CMD_NOTE_ON | _channel);
448         event[1] = note;
449         event[2] = _audition_velocity.get_value_as_int ();
450         mt->write_immediate_event (3, event);
451         _audition_note_on = true;
452         _audition_note_num = note;
453 }
454
455 void
456 PatchChangeWidget::note_off_event_handler (int note)
457 {
458         boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
459         uint8_t event[3];
460         event[0] = (MIDI_CMD_NOTE_OFF | _channel);
461         event[1] = note;
462         event[2] = 0;
463         mt->write_immediate_event (3, event);
464         _audition_note_on = false;
465 }
466
467 /* ***** query info *****/
468
469 int
470 PatchChangeWidget::bank (uint8_t chn) const
471 {
472         boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
473         boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
474
475         return ((int)bank_msb->get_value () << 7) + (int)bank_lsb->get_value();
476 }
477
478 uint8_t
479 PatchChangeWidget::program (uint8_t chn) const
480 {
481         boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
482         return program->get_value();
483 }
484
485 /* ***************************************************************************/
486
487 PatchChangeGridDialog::PatchChangeGridDialog (boost::shared_ptr<ARDOUR::Route> r)
488         : ArdourDialog (string_compose (_("Select Patch for '%1"), r->name()), false, false)
489         , w (r)
490 {
491         r->PropertyChanged.connect (_route_connection, invalidator (*this), boost::bind (&PatchChangeGridDialog::route_property_changed, this, _1, boost::weak_ptr<Route>(r)), gui_context());
492         get_vbox()->add (w);
493         w.show ();
494 }
495
496 void
497 PatchChangeGridDialog::route_property_changed (const PBD::PropertyChange& what_changed, boost::weak_ptr<Route> wr)
498 {
499         boost::shared_ptr<ARDOUR::Route> r = wr.lock ();
500         if (r && what_changed.contains (ARDOUR::Properties::name)) {
501                 set_title (string_compose (_("Select Patch for '%1"), r->name()));
502         }
503 }