2 * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3 * Copyright (C) 2011 Paul Davis
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.
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.
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.
20 #include <gtkmm/frame.h>
21 #include <boost/algorithm/string.hpp>
23 #include "pbd/unwind.h"
25 #include "evoral/midi_events.h"
26 #include "evoral/PatchChange.hpp"
28 #include "midi++/midnam_patch.h"
30 #include "ardour/instrument_info.h"
31 #include "ardour/midi_track.h"
33 #include "widgets/tooltips.h"
35 #include "gui_thread.h"
36 #include "patch_change_widget.h"
37 #include "ui_config.h"
42 using namespace ARDOUR;
44 PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::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)
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)
57 assert (boost::dynamic_pointer_cast<MidiTrack> (r));
60 box = manage (new HBox ());
61 box->set_border_width (2);
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);
72 pack_start (*box, false, false);
74 _program_table.set_spacings (1);
75 pack_start (_program_table, true, true);
77 box = manage (new HBox ());
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);
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);
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 ());
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));
99 using namespace Menu_Helpers;
100 for (uint32_t chn = 0; chn < 16; ++chn) {
102 snprintf (buf, sizeof(buf), "%d", chn + 1);
103 _channel_select.AddMenuElem (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_channel), chn)));
106 _audition_start_spin.set_sensitive (false);
107 _audition_end_spin.set_sensitive (false);
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));
115 _info.Changed.connect (_info_changed_connection, invalidator (*this),
116 boost::bind (&PatchChangeWidget::instrument_info_changed, this), gui_context());
122 PatchChangeWidget::~PatchChangeWidget ()
128 PatchChangeWidget::on_show ()
130 Gtk::VBox::on_show ();
137 PatchChangeWidget::on_hide ()
139 Gtk::VBox::on_hide ();
140 _ac_connections.drop_connections ();
145 PatchChangeWidget::select_channel (uint8_t chn)
150 if (_channel == chn) {
156 _channel_select.set_text (string_compose ("%1", (int)(chn + 1)));
159 _ac_connections.drop_connections ();
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);
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 ());
176 PatchChangeWidget::refill_banks ()
179 using namespace Menu_Helpers;
181 _current_patch_bank.reset ();
182 _bank_select.clear_items ();
184 const int b = bank (_channel);
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);
192 boost::shared_ptr<MIDI::Name::ChannelNameSet> cns = _info.get_patches (_channel);
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);
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);
211 refill_program_list ();
215 PatchChangeWidget::refill_program_list ()
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);
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 ();
231 assert (key.program () < 128);
232 assert (key.bank () == bank (_channel));
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)));
243 /* ***** user GUI actions *****/
246 PatchChangeWidget::select_bank_spin ()
248 if (_ignore_spin_btn_signals) {
251 const uint32_t b = (_bank_msb_spin.get_value_as_int() << 7) + _bank_lsb_spin.get_value_as_int();
256 PatchChangeWidget::select_bank (uint32_t bank)
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);
263 bank_msb->set_value (bank >> 7, PBD::Controllable::NoGroup);
264 bank_lsb->set_value (bank & 127, PBD::Controllable::NoGroup);
268 PatchChangeWidget::select_program (uint8_t pgm)
272 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, _channel), true);
273 program->set_value (pgm, PBD::Controllable::NoGroup);
278 /* ***** callbacks, external changes *****/
281 PatchChangeWidget::bank_changed ()
283 // TODO optimize, just find the bank, set the text and refill_program_list()
288 PatchChangeWidget::program_changed ()
290 uint8_t p = program (_channel);
291 for (uint8_t pgm = 0; pgm < 128; ++pgm) {
292 _program_btn[pgm].set_active (pgm == p);
297 PatchChangeWidget::instrument_info_changed ()
302 /* ***** play notes *****/
305 PatchChangeWidget::audition_toggle ()
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);
313 _audition_start_spin.set_sensitive (false);
314 _audition_end_spin.set_sensitive (false);
319 PatchChangeWidget::check_note_range (bool upper)
321 int s = _audition_start_spin.get_value_as_int ();
322 int e = _audition_end_spin.get_value_as_int ();
327 _audition_start_spin.set_value (e);
329 _audition_end_spin.set_value (s);
334 PatchChangeWidget::cancel_audition ()
336 _note_queue_connection.disconnect();
338 if (_audition_note_on) {
339 boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
342 event[0] = (MIDI_CMD_NOTE_OFF | _channel);
343 event[1] = _audition_note_num;
345 mt->write_immediate_event(3, event);
347 _audition_note_on = false;
351 PatchChangeWidget::audition ()
353 if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
360 if (_note_queue_connection.connected ()) {
364 if (!_audition_enable.get_active ()) {
368 assert (!_audition_note_on);
369 _audition_note_num = _audition_start_spin.get_value_as_int ();
371 _note_queue_connection = Glib::signal_timeout().connect (sigc::bind (sigc::mem_fun (&PatchChangeWidget::audition_next), this), 250);
375 PatchChangeWidget::audition_next ()
377 boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
380 if (_audition_note_on) {
381 event[0] = (MIDI_CMD_NOTE_OFF | _channel);
382 event[1] = _audition_note_num;
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 ();
388 event[0] = (MIDI_CMD_NOTE_ON | _channel);
389 event[1] = _audition_note_num;
391 mt->write_immediate_event(3, event);
392 _audition_note_on = true;
397 /* ***** query info *****/
400 PatchChangeWidget::bank (uint8_t chn) const
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);
405 return ((int)bank_msb->get_value () << 7) + (int)bank_lsb->get_value();
409 PatchChangeWidget::program (uint8_t chn) const
411 boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
412 return program->get_value();
415 /* ***************************************************************************/
417 PatchChangeGridDialog::PatchChangeGridDialog (std::string const& title, boost::shared_ptr<ARDOUR::Route> r)
418 : ArdourDialog (title, false, false)