Rework Patch-Change/Select Dialog
authorRobin Gareus <robin@gareus.org>
Thu, 7 Sep 2017 22:30:55 +0000 (00:30 +0200)
committerRobin Gareus <robin@gareus.org>
Fri, 8 Sep 2017 00:19:31 +0000 (02:19 +0200)
gtk2_ardour/midi_time_axis.cc
gtk2_ardour/midi_time_axis.h
gtk2_ardour/patch_change_widget.cc [new file with mode: 0644]
gtk2_ardour/patch_change_widget.h [new file with mode: 0644]
gtk2_ardour/wscript

index 33160f8da3e185fb2c07167563d351b393434f2a..ca672447316365ad2da8b2174f993d137e623c92 100644 (file)
@@ -77,6 +77,7 @@
 #include "midi_region_view.h"
 #include "midi_time_axis.h"
 #include "patch_change_dialog.h"
+#include "patch_change_widget.h"
 #include "piano_roll_header.h"
 #include "playlist_selector.h"
 #include "plugin_selector.h"
@@ -1081,33 +1082,6 @@ MidiTimeAxisView::build_color_mode_menu()
        return mode_menu;
 }
 
-void
-MidiTimeAxisView::immediate_patch_chnage_response (int response)
-{
-       if (response != RESPONSE_ACCEPT || !_route) {
-               delete  _patch_change_dialog;
-               _patch_change_dialog = 0;
-               return;
-       }
-       Evoral::PatchChange<Evoral::Beats> p (_patch_change_dialog->patch ());
-
-       uint8_t chn = p.channel();
-
-       boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
-       boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
-       boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
-
-       if (!bank_msb || ! bank_lsb || !program) {
-               _patch_change_dialog->show ();
-               return;
-       }
-
-       bank_msb->set_value (p.bank_msb (), Controllable::NoGroup);
-       bank_lsb->set_value (p.bank_lsb (), Controllable::NoGroup);
-       program->set_value  (p.program () , Controllable::NoGroup);
-       _patch_change_dialog->show ();
-}
-
 void
 MidiTimeAxisView::send_patch_change ()
 {
@@ -1119,11 +1093,9 @@ MidiTimeAxisView::send_patch_change ()
                return;
        }
 
-       Evoral::PatchChange<Evoral::Beats> empty (Evoral::Beats(), 0, 0, 0);
-       PatchChangeDialog* d = new PatchChangeDialog (0, 0, empty, _route->instrument_info(), Gtk::Stock::APPLY, false, false);
-       d->signal_response().connect (sigc::mem_fun (*this, &MidiTimeAxisView::immediate_patch_chnage_response));
+       PatchChangeGridDialog* d = new PatchChangeGridDialog (string_compose (_("Select Patch for '%1'"), _route->name ()), _route);
        _patch_change_dialog = d;
-       _patch_change_dialog->present ();
+       d->present ();
 }
 
 void
index 6bbb3594581cb515156ebd241ae0a555ac0378c8..72167204ed023c7f8d398c0044c6d5a1518b58dc 100644 (file)
@@ -67,7 +67,7 @@ class PianoRollHeader;
 class StepEntry;
 class StepEditor;
 class MidiChannelSelectorWindow;
-class PatchChangeDialog;
+class PatchChangeGridDialog;
 
 #define NO_MIDI_NOTE 0xff
 
@@ -199,7 +199,7 @@ private:
        StepEditor* _step_editor;
 
        void immediate_patch_chnage_response (int response);
-       PatchChangeDialog* _patch_change_dialog;
+       PatchChangeGridDialog* _patch_change_dialog;
 
 };
 
diff --git a/gtk2_ardour/patch_change_widget.cc b/gtk2_ardour/patch_change_widget.cc
new file mode 100644 (file)
index 0000000..e4409b1
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2011 Paul Davis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <gtkmm/frame.h>
+#include <boost/algorithm/string.hpp>
+
+#include "pbd/unwind.h"
+#include "evoral/PatchChange.hpp"
+#include "midi++/midnam_patch.h"
+#include "ardour/instrument_info.h"
+#include "ardour/midi_track.h"
+
+#include "widgets/tooltips.h"
+
+#include "gui_thread.h"
+#include "patch_change_widget.h"
+#include "ui_config.h"
+
+#include "pbd/i18n.h"
+
+using namespace Gtk;
+using namespace ARDOUR;
+
+PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
+       : _route (r)
+       , _bank_msb_spin (*manage (new Adjustment (0, 0, 127, 1, 16)))
+       , _bank_lsb_spin (*manage (new Adjustment (0, 0, 127, 1, 16)))
+       , _program_table (/*rows*/ 16, /*cols*/ 8, true)
+       , _channel (-1)
+       , _ignore_spin_btn_signals (false)
+       , _info (r->instrument_info ())
+{
+       assert (boost::dynamic_pointer_cast<MidiTrack> (r));
+
+       Box* box;
+       box = manage (new HBox ());
+       box->set_border_width (2);
+       box->set_spacing (4);
+       box->pack_start (*manage (new Label (_("Channel:"))), false, false);
+       box->pack_start (_channel_select, false, false);
+       box->pack_start (*manage (new Label (_("Bank:"))), false, false);
+       box->pack_start (_bank_select, true, true);
+       box->pack_start (*manage (new Label (_("MSB:"))), false, false);
+       box->pack_start (_bank_msb_spin, false, false);
+       box->pack_start (*manage (new Label (_("LSB:"))), false, false);
+       box->pack_start (_bank_lsb_spin, false, false);
+
+       pack_start (*box, false, false);
+
+       _program_table.set_spacings (1);
+       pack_start (_program_table, true, true);
+
+       for (uint8_t pgm = 0; pgm < 128; ++pgm) {
+               _program_btn[pgm].set_text_ellipsize (Pango::ELLIPSIZE_END);
+               _program_btn[pgm].set_layout_ellipsize_width (PANGO_SCALE * 112 * UIConfiguration::instance ().get_ui_scale ());
+               int row = pgm % 16;
+               int col = pgm / 16;
+               _program_table.attach (_program_btn[pgm], col, col + 1, row, row + 1);
+               _program_btn[pgm].signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_program), pgm));
+       }
+
+       using namespace Menu_Helpers;
+       for (uint32_t chn = 0; chn < 16; ++chn) {
+               char buf[8];
+               snprintf (buf, sizeof(buf), "%d", chn + 1);
+               _channel_select.AddMenuElem (MenuElem (buf, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_channel), chn)));
+       }
+
+       _bank_msb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
+       _bank_lsb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchChangeWidget::select_bank_spin));
+
+       _info.Changed.connect (_info_changed_connection, invalidator (*this),
+                       boost::bind (&PatchChangeWidget::instrument_info_changed, this), gui_context());
+
+       set_spacing (4);
+       show_all ();
+}
+
+PatchChangeWidget::~PatchChangeWidget ()
+{
+}
+
+void
+PatchChangeWidget::on_show ()
+{
+       Gtk::VBox::on_show ();
+       _channel = -1;
+       select_channel (0);
+}
+
+void
+PatchChangeWidget::on_hide ()
+{
+       Gtk::VBox::on_hide ();
+       _ac_connections.drop_connections ();
+}
+
+
+void
+PatchChangeWidget::select_channel (uint8_t chn)
+{
+       assert (_route);
+
+       if (_channel == chn) {
+               return;
+       }
+
+       _channel_select.set_text (string_compose ("%1", (int)(chn + 1)));
+       _channel = chn;
+
+       _ac_connections.drop_connections ();
+
+       boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
+       boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
+       boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true); 
+
+       bank_msb->Changed.connect (_ac_connections, invalidator (*this),
+                       boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
+       bank_lsb->Changed.connect (_ac_connections, invalidator (*this),
+                       boost::bind (&PatchChangeWidget::bank_changed, this), gui_context ());
+       program->Changed.connect (_ac_connections, invalidator (*this),
+                       boost::bind (&PatchChangeWidget::program_changed, this), gui_context ());
+
+       refill_banks ();
+}
+
+void
+PatchChangeWidget::refill_banks ()
+{
+       using namespace Menu_Helpers;
+
+       _current_patch_bank.reset ();
+       _bank_select.clear_items ();
+
+       const int b = bank (_channel);
+
+       {
+               PBD::Unwinder<bool> (_ignore_spin_btn_signals, true);
+               _bank_msb_spin.set_value (b >> 7);
+               _bank_lsb_spin.set_value (b & 127);
+       }
+
+       boost::shared_ptr<MIDI::Name::ChannelNameSet> cns = _info.get_patches (_channel);
+       if (cns) {
+               for (MIDI::Name::ChannelNameSet::PatchBanks::const_iterator i = cns->patch_banks().begin(); i != cns->patch_banks().end(); ++i) {
+                       std::string n = (*i)->name ();
+                       boost::replace_all (n, "_", " ");
+                       _bank_select.AddMenuElem (MenuElem (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), (*i)->number ())));
+                       if ((*i)->number () == b) {
+                               _current_patch_bank = *i;
+                               _bank_select.set_text (n);
+                       }
+               }
+       }
+
+       if (!_current_patch_bank) {
+               std::string n = string_compose (_("Bank %1"), b);
+               _bank_select.AddMenuElem (MenuElem (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), b)));
+               _bank_select.set_text (n);
+       }
+
+       refill_program_list ();
+}
+
+void
+PatchChangeWidget::refill_program_list ()
+{
+       // TODO if _current_patch_bank!=0, only clear/reset unused patches
+       for (uint8_t pgm = 0; pgm < 128; ++pgm) {
+               std::string n = string_compose (_("Pgm-%1"), (int)(pgm +1));
+               _program_btn[pgm].set_text (n);
+               set_tooltip (_program_btn[pgm], n);
+       }
+
+       if (_current_patch_bank) {
+               const MIDI::Name::PatchNameList& patches = _current_patch_bank->patch_name_list ();
+               for (MIDI::Name::PatchNameList::const_iterator i = patches.begin(); i != patches.end(); ++i) {
+                       std::string n = (*i)->name ();
+                       boost::replace_all (n, "_", " ");
+                       MIDI::Name::PatchPrimaryKey const& key = (*i)->patch_primary_key ();
+
+                       assert (key.program () < 128);
+                       assert (key.bank () == bank (_channel));
+
+                       const uint8_t pgm = key.program();
+                       _program_btn[pgm].set_text (n);
+                       set_tooltip (_program_btn[pgm], string_compose (_("%1 (Pgm-%2)"), n, (int)(pgm +1)));
+                       }
+       }
+
+       program_changed ();
+}
+
+/* ***** user GUI actions *****/
+
+void
+PatchChangeWidget::select_bank_spin ()
+{
+       if (_ignore_spin_btn_signals) {
+               return;
+       }
+       const uint32_t b = (_bank_msb_spin.get_value_as_int() << 7) + _bank_lsb_spin.get_value_as_int();
+       select_bank (b);
+}
+
+void
+PatchChangeWidget::select_bank (uint32_t bank)
+{
+       boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_MSB_BANK), true);
+       boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, _channel, MIDI_CTL_LSB_BANK), true);
+
+       bank_msb->set_value (bank >> 7, PBD::Controllable::NoGroup);
+       bank_lsb->set_value (bank & 127, PBD::Controllable::NoGroup);
+}
+
+void
+PatchChangeWidget::select_program (uint8_t pgm)
+{
+       boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, _channel), true);
+       program->set_value (pgm, PBD::Controllable::NoGroup);
+}
+
+/* ***** callbacks, external changes *****/
+
+void
+PatchChangeWidget::bank_changed ()
+{
+       // TODO optimize, just find the bank, set the text and refill_program_list()
+       refill_banks ();
+}
+
+void
+PatchChangeWidget::program_changed ()
+{
+       uint8_t p = program (_channel);
+       for (uint8_t pgm = 0; pgm < 128; ++pgm) {
+               _program_btn[pgm].set_active (pgm == p);
+       }
+}
+
+void
+PatchChangeWidget::instrument_info_changed ()
+{
+       refill_banks ();
+}
+
+
+/* ***** query info *****/
+
+int
+PatchChangeWidget::bank (uint8_t chn) const
+{
+       boost::shared_ptr<AutomationControl> bank_msb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_MSB_BANK), true);
+       boost::shared_ptr<AutomationControl> bank_lsb = _route->automation_control(Evoral::Parameter (MidiCCAutomation, chn, MIDI_CTL_LSB_BANK), true);
+
+       return ((int)bank_msb->get_value () << 7) + (int)bank_lsb->get_value();
+}
+
+uint8_t 
+PatchChangeWidget::program (uint8_t chn) const
+{
+       boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true); 
+       return program->get_value();
+}
+
+/* ***************************************************************************/
+
+PatchChangeGridDialog::PatchChangeGridDialog (std::string const& title, boost::shared_ptr<ARDOUR::Route> r)
+       : ArdourDialog (title, false, false)
+       , w (r)
+{
+       get_vbox()->add (w);
+       w.show ();
+}
diff --git a/gtk2_ardour/patch_change_widget.h b/gtk2_ardour/patch_change_widget.h
new file mode 100644 (file)
index 0000000..89ed380
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __gtkardour_patch_change_widget_h__
+#define __gtkardour_patch_change_widget_h__
+
+#include <gtkmm/box.h>
+#include <gtkmm/spinbutton.h>
+#include <gtkmm/table.h>
+
+#include "pbd/signals.h"
+
+#include "ardour/route.h"
+
+#include "widgets/ardour_button.h"
+#include "widgets/ardour_dropdown.h"
+
+#include "ardour_dialog.h"
+
+class PatchChangeWidget : public Gtk::VBox
+{
+public:
+       PatchChangeWidget (boost::shared_ptr<ARDOUR::Route>);
+       ~PatchChangeWidget ();
+
+protected:
+       int bank (uint8_t) const;
+       uint8_t program (uint8_t) const;
+
+       void on_show ();
+       void on_hide ();
+
+private:
+       boost::shared_ptr<ARDOUR::Route> _route;
+
+       ArdourWidgets::ArdourDropdown _channel_select;
+       ArdourWidgets::ArdourDropdown _bank_select;
+       Gtk::SpinButton               _bank_msb_spin;
+       Gtk::SpinButton               _bank_lsb_spin;
+       ArdourWidgets::ArdourButton   _program_btn[128];
+       Gtk::Table                    _program_table;
+
+       uint8_t _channel;
+       bool    _ignore_spin_btn_signals;
+
+       void select_channel (uint8_t);
+       void select_bank (uint32_t);
+       void select_bank_spin ();
+       void select_program (uint8_t);
+
+       void bank_changed ();
+       void program_changed ();
+
+       void refill_banks ();
+       void refill_program_list ();
+
+       void instrument_info_changed ();
+
+       PBD::ScopedConnection _info_changed_connection;
+       PBD::ScopedConnection _route_connection;
+       PBD::ScopedConnectionList _ac_connections;
+
+       ARDOUR::InstrumentInfo& _info;
+       boost::shared_ptr<MIDI::Name::PatchBank> _current_patch_bank;
+};
+
+class PatchChangeGridDialog : public ArdourDialog
+{
+public:
+       PatchChangeGridDialog (std::string const&, boost::shared_ptr<ARDOUR::Route>);
+       void on_hide () { w.hide (); ArdourDialog::on_hide (); }
+       void on_show () { w.show (); ArdourDialog::on_show (); }
+
+private:
+       PatchChangeWidget w;
+};
+
+#endif
index 598674079ae865704c7fa86793b99df66c6c6b6a..d3824a0a63e4fdbf05961d31ef0c74a4bbaf4436 100644 (file)
@@ -178,6 +178,7 @@ gtk2_ardour_sources = [
         'panner_interface.cc',
         'panner_ui.cc',
         'patch_change.cc',
+        'patch_change_widget.cc',
         'piano_roll_header.cc',
         'pingback.cc',
         'playlist_selector.cc',