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