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