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