* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#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"
, _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)))
_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 ());
_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) {
+ using namespace Menu_Helpers;
+ using namespace Gtkmm2ext;
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)));
+ _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);
_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 ();
}
delete _pianomm;
}
+void
+PatchChangeWidget::refresh ()
+{
+ if (is_visible ()) {
+ on_show ();
+ }
+}
+
void
PatchChangeWidget::on_show ()
{
_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 ();
}
{
cancel_audition ();
using namespace Menu_Helpers;
+ using namespace Gtkmm2ext;
_current_patch_bank.reset ();
_bank_select.clear_items ();
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 () == 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;
_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.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), b)));
_bank_select.set_text (n);
}
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);
- }
+ std::bitset<128> unset_notes;
+ unset_notes.set ();
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, "_", " ");
+ 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);
}
program_changed ();
{
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));
}
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 ();
}
void
PatchChangeWidget::bank_changed ()
{
- // TODO optimize, just find the bank, set the text and refill_program_list()
refill_banks ();
}
+void
+PatchChangeWidget::bankpatch_changed (uint8_t chn)
+{
+ if (chn == _channel) {
+ refill_banks ();
+ }
+}
+
void
PatchChangeWidget::program_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
_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) {
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;
}
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
}
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 *****/
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()));
+ }
+}