Prepare central engine check and user notification
[ardour.git] / gtk2_ardour / patch_change_widget.cc
index 32ff85501d095f428860501091e7aa0b42872524..f9c88d80688af5845799a1204200d4c105fab932 100644 (file)
@@ -19,7 +19,6 @@
 
 #include <bitset>
 #include <gtkmm/frame.h>
-#include <boost/algorithm/string.hpp>
 
 #include "pbd/unwind.h"
 
 
 #include "ardour/instrument_info.h"
 #include "ardour/midi_track.h"
+#include "ardour/plugin_insert.h"
 
 #include "gtkmm2ext/menu_elems.h"
+#include "gtkmm2ext/utils.h"
 #include "widgets/tooltips.h"
 
 #include "gui_thread.h"
@@ -50,6 +51,7 @@ PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
        , _program_table (/*rows*/ 16, /*cols*/ 8, true)
        , _channel (-1)
        , _ignore_spin_btn_signals (false)
+       , _no_notifications (false)
        , _info (r->instrument_info ())
        , _audition_enable (_("Audition on Change"), ArdourWidgets::ArdourButton::led_default_elements)
        , _audition_start_spin (*manage (new Adjustment (48, 0, 127, 1, 16)))
@@ -77,23 +79,25 @@ PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
        _program_table.set_spacings (1);
        pack_start (_program_table, true, true);
 
-       if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
-               box = manage (new HBox ());
-               box->set_spacing (4);
-               box->pack_start (_audition_enable, false, false);
-               box->pack_start (*manage (new Label (_("Start Note:"))), false, false);
-               box->pack_start (_audition_start_spin, false, false);
-               box->pack_start (*manage (new Label (_("End Note:"))), false, false);
-               box->pack_start (_audition_end_spin, false, false);
-               box->pack_start (*manage (new Label (_("Velocity:"))), false, false);
-               box->pack_start (_audition_velocity, false, false);
-
-               Box* box2 = manage (new HBox ());
-               box2->pack_start (*box, true, false);
-               box2->set_border_width (2);
-               pack_start (*box2, false, false);
+       if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               pack_start ( *manage (new Label (_("Note: Patch Selection is volatile (only Midi-Tracks retain bank/patch selection)."))), false, false);
        }
 
+       box = manage (new HBox ());
+       box->set_spacing (4);
+       box->pack_start (_audition_enable, false, false);
+       box->pack_start (*manage (new Label (_("Start Note:"))), false, false);
+       box->pack_start (_audition_start_spin, false, false);
+       box->pack_start (*manage (new Label (_("End Note:"))), false, false);
+       box->pack_start (_audition_end_spin, false, false);
+       box->pack_start (*manage (new Label (_("Velocity:"))), false, false);
+       box->pack_start (_audition_velocity, false, false);
+
+       Box* box2 = manage (new HBox ());
+       box2->pack_start (*box, true, false);
+       box2->set_border_width (2);
+       pack_start (*box2, false, false);
+
        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 ());
@@ -111,13 +115,11 @@ PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
                _channel_select.AddMenuElem (MenuElemNoMnemonic (buf, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_channel), chn)));
        }
 
-       if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
-               piano_keyboard_set_monophonic (_piano, TRUE);
-               g_signal_connect (G_OBJECT (_piano), "note-on", G_CALLBACK (PatchChangeWidget::_note_on_event_handler), this);
-               g_signal_connect (G_OBJECT (_piano), "note-off", G_CALLBACK (PatchChangeWidget::_note_off_event_handler), this);
-               _pianomm->set_flags(Gtk::CAN_FOCUS);
-               pack_start (*_pianomm, false, false);
-       }
+       piano_keyboard_set_monophonic (_piano, TRUE);
+       g_signal_connect (G_OBJECT (_piano), "note-on", G_CALLBACK (PatchChangeWidget::_note_on_event_handler), this);
+       g_signal_connect (G_OBJECT (_piano), "note-off", G_CALLBACK (PatchChangeWidget::_note_off_event_handler), this);
+       _pianomm->set_flags(Gtk::CAN_FOCUS);
+       pack_start (*_pianomm, false, false);
 
        _audition_start_spin.set_sensitive (false);
        _audition_end_spin.set_sensitive (false);
@@ -131,6 +133,12 @@ PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
        _info.Changed.connect (_info_changed_connection, invalidator (*this),
                        boost::bind (&PatchChangeWidget::instrument_info_changed, this), gui_context());
 
+       if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               _route->processors_changed.connect (_route_connection, invalidator (*this),
+                               boost::bind (&PatchChangeWidget::processors_changed, this), gui_context());
+               processors_changed ();
+       }
+
        set_spacing (4);
        show_all ();
 }
@@ -141,6 +149,14 @@ PatchChangeWidget::~PatchChangeWidget ()
        delete _pianomm;
 }
 
+void
+PatchChangeWidget::refresh ()
+{
+       if (is_visible ()) {
+               on_show ();
+       }
+}
+
 void
 PatchChangeWidget::on_show ()
 {
@@ -172,19 +188,30 @@ PatchChangeWidget::select_channel (uint8_t chn)
 
        _channel_select.set_text (string_compose ("%1", (int)(chn + 1)));
        _channel = chn;
+       _no_notifications = false;
 
        _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 ());
+       if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               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 ());
+       } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
+               if (pi->plugin()->knows_bank_patch ()) {
+                       pi->plugin ()->BankPatchChange.connect (_ac_connections, invalidator (*this),
+                                       boost::bind (&PatchChangeWidget::bankpatch_changed, this, _1), gui_context ());
+               } else {
+                       _no_notifications = true;
+                       // TODO add note: instrument does not report changes.
+               }
+       }
 
        refill_banks ();
 }
@@ -211,6 +238,9 @@ PatchChangeWidget::refill_banks ()
        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 ();
+                       if ((*i)->number () == UINT16_MAX) {
+                               continue;
+                       }
                        _bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), (*i)->number ())));
                        if ((*i)->number () == b) {
                                _current_patch_bank = *i;
@@ -237,22 +267,25 @@ PatchChangeWidget::refill_program_list ()
        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 ();
+                       const std::string n = (*i)->name ();
                        MIDI::Name::PatchPrimaryKey const& key = (*i)->patch_primary_key ();
 
                        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)));
+                       set_tooltip (_program_btn[pgm], string_compose (_("%1 (Pgm-%2)"),
+                                               Gtkmm2ext::markup_escape_text (n), (int)(pgm +1)));
                        unset_notes.reset (pgm);
                }
        }
 
        for (uint8_t pgm = 0; pgm < 128; ++pgm) {
                if (!unset_notes.test (pgm)) {
+                       _program_btn[pgm].set_name (X_("patch change button"));
                        continue;
                }
                std::string n = string_compose (_("Pgm-%1"), (int)(pgm +1));
                _program_btn[pgm].set_text (n);
+               _program_btn[pgm].set_name (X_("patch change dim button"));
                set_tooltip (_program_btn[pgm], n);
        }
 
@@ -276,11 +309,24 @@ PatchChangeWidget::select_bank (uint32_t bank)
 {
        cancel_audition ();
 
-       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);
+       if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               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);
+       } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
+               uint8_t event[3];
+               event[0] = (MIDI_CMD_CONTROL | _channel);
+               event[1] = 0x00;
+               event[2] = bank >> 7;
+               pi->write_immediate_event (3, event);
+
+               event[1] = 0x20;
+               event[2] = bank & 127;
+               pi->write_immediate_event (3, event);
+       }
 
-       bank_msb->set_value (bank >> 7, PBD::Controllable::NoGroup);
-       bank_lsb->set_value (bank & 127, PBD::Controllable::NoGroup);
        select_program (program (_channel));
 }
 
@@ -288,9 +334,23 @@ void
 PatchChangeWidget::select_program (uint8_t pgm)
 {
        cancel_audition ();
+       if (_no_notifications) {
+               program_changed ();
+       }
 
-       boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, _channel), true);
-       program->set_value (pgm, PBD::Controllable::NoGroup);
+       if (pgm > 127) {
+               return;
+       }
+
+       if (boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, _channel), true);
+               program->set_value (pgm, PBD::Controllable::NoGroup);
+       } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
+               uint8_t event[2];
+               event[0] = (MIDI_CMD_PGM_CHANGE | _channel);
+               event[1] = pgm;
+               pi->write_immediate_event (2, event);
+       }
 
        audition ();
 }
@@ -303,6 +363,14 @@ PatchChangeWidget::bank_changed ()
        refill_banks ();
 }
 
+void
+PatchChangeWidget::bankpatch_changed (uint8_t chn)
+{
+       if (chn == _channel) {
+               refill_banks ();
+       }
+}
+
 void
 PatchChangeWidget::program_changed ()
 {
@@ -318,6 +386,17 @@ PatchChangeWidget::instrument_info_changed ()
        refill_banks ();
 }
 
+void
+PatchChangeWidget::processors_changed ()
+{
+       assert (!boost::dynamic_pointer_cast<MidiTrack> (_route));
+       if (_route->the_instrument ()) {
+               set_sensitive (true);
+       } else {
+               set_sensitive (false);
+       }
+}
+
 /* ***** play notes *****/
 
 void
@@ -355,22 +434,15 @@ PatchChangeWidget::cancel_audition ()
        _note_queue_connection.disconnect();
 
        if (_audition_note_on) {
-               boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
-               uint8_t event[3];
-
-               event[0] = (MIDI_CMD_NOTE_OFF | _channel);
-               event[1] = _audition_note_num;
-               event[2] = 0;
-               mt->write_immediate_event (3, event);
+               note_off_event_handler (_audition_note_num);
                piano_keyboard_set_note_off (_piano, _audition_note_num);
        }
-       _audition_note_on = false;
 }
 
 void
 PatchChangeWidget::audition ()
 {
-       if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+       if (!boost::dynamic_pointer_cast<MidiTrack> (_route) && !boost::dynamic_pointer_cast<PluginInsert> (_route)) {
                return;
        }
        if (_channel > 16) {
@@ -394,23 +466,12 @@ PatchChangeWidget::audition ()
 bool
 PatchChangeWidget::audition_next ()
 {
-       boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
-       uint8_t event[3];
-
        if (_audition_note_on) {
-               event[0] = (MIDI_CMD_NOTE_OFF | _channel);
-               event[1] = _audition_note_num;
-               event[2] = 0;
-               mt->write_immediate_event (3, event);
-               _audition_note_on = false;
+               note_off_event_handler (_audition_note_num);
                piano_keyboard_set_note_off (_piano, _audition_note_num);
                return ++_audition_note_num <= _audition_end_spin.get_value_as_int() && _audition_enable.get_active ();
        } else {
-               event[0] = (MIDI_CMD_NOTE_ON | _channel);
-               event[1] = _audition_note_num;
-               event[2] = _audition_velocity.get_value_as_int ();
-               mt->write_immediate_event (3, event);
-               _audition_note_on = true;
+               note_on_event_handler (_audition_note_num, true);
                piano_keyboard_set_note_on (_piano, _audition_note_num);
                return true;
        }
@@ -419,7 +480,7 @@ PatchChangeWidget::audition_next ()
 void
 PatchChangeWidget::_note_on_event_handler(GtkWidget*, int note, gpointer arg)
 {
-       ((PatchChangeWidget*)arg)->note_on_event_handler(note);
+       ((PatchChangeWidget*)arg)->note_on_event_handler(note, false);
 }
 
 void
@@ -429,30 +490,40 @@ PatchChangeWidget::_note_off_event_handler(GtkWidget*, int note, gpointer arg)
 }
 
 void
-PatchChangeWidget::note_on_event_handler (int note)
+PatchChangeWidget::note_on_event_handler (int note, bool for_audition)
 {
-       cancel_audition ();
-       _pianomm->grab_focus ();
-       boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
+       if (!for_audition) {
+               cancel_audition ();
+               _pianomm->grab_focus ();
+       }
        uint8_t event[3];
        event[0] = (MIDI_CMD_NOTE_ON | _channel);
        event[1] = note;
        event[2] = _audition_velocity.get_value_as_int ();
-       mt->write_immediate_event (3, event);
        _audition_note_on = true;
        _audition_note_num = note;
+
+       if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               mt->write_immediate_event (3, event);
+       } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
+               pi->write_immediate_event (3, event);
+       }
 }
 
 void
 PatchChangeWidget::note_off_event_handler (int note)
 {
-       boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route);
        uint8_t event[3];
        event[0] = (MIDI_CMD_NOTE_OFF | _channel);
        event[1] = note;
        event[2] = 0;
-       mt->write_immediate_event (3, event);
        _audition_note_on = false;
+
+       if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               mt->write_immediate_event (3, event);
+       } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
+               pi->write_immediate_event (3, event);
+       }
 }
 
 /* ***** query info *****/
@@ -460,25 +531,51 @@ PatchChangeWidget::note_off_event_handler (int note)
 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();
+       if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               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();
+       } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
+               uint32_t bankpatch = pi->plugin()->bank_patch (chn);
+               if (bankpatch != UINT32_MAX) {
+                       return bankpatch >> 7;
+               }
+       }
+       return 0;
 }
 
 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();
+       if (boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (_route)) {
+               boost::shared_ptr<AutomationControl> program = _route->automation_control(Evoral::Parameter (MidiPgmChangeAutomation, chn), true);
+               return program->get_value();
+       } else if (boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (_route->the_instrument())) {
+               uint32_t bankpatch = pi->plugin()->bank_patch (chn);
+               if (bankpatch != UINT32_MAX) {
+                       return bankpatch & 127;
+               }
+       }
+       return 255;
 }
 
 /* ***************************************************************************/
 
-PatchChangeGridDialog::PatchChangeGridDialog (std::string const& title, boost::shared_ptr<ARDOUR::Route> r)
-       : ArdourDialog (title, false, false)
+PatchChangeGridDialog::PatchChangeGridDialog (boost::shared_ptr<ARDOUR::Route> r)
+       : ArdourDialog (string_compose (_("Select Patch for \"%1\""), r->name()), false, false)
        , w (r)
 {
+       r->PropertyChanged.connect (_route_connection, invalidator (*this), boost::bind (&PatchChangeGridDialog::route_property_changed, this, _1, boost::weak_ptr<Route>(r)), gui_context());
        get_vbox()->add (w);
        w.show ();
 }
+
+void
+PatchChangeGridDialog::route_property_changed (const PBD::PropertyChange& what_changed, boost::weak_ptr<Route> wr)
+{
+       boost::shared_ptr<ARDOUR::Route> r = wr.lock ();
+       if (r && what_changed.contains (ARDOUR::Properties::name)) {
+               set_title (string_compose (_("Select Patch for \"%1\"'"), r->name()));
+       }
+}