Abstract Patch Change Widget, prepare to reuse for Slots

This commit is contained in:
Robin Gareus 2022-02-08 01:25:19 +01:00
parent 9b21c2be54
commit 51f458220d
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
2 changed files with 254 additions and 204 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2017-2022 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
@ -45,15 +45,213 @@
using namespace Gtk;
using namespace ARDOUR;
PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
PatchBankList::PatchBankList (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)
, _no_notifications (false)
, _info (r->instrument_info ())
, _ignore_spin_btn_signals (false)
{
_program_table.set_spacings (1);
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, &PatchBankList::select_program), pgm));
}
_bank_msb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchBankList::select_bank_spin));
_bank_lsb_spin.signal_changed().connect (sigc::mem_fun (*this, &PatchBankList::select_bank_spin));
_info.Changed.connect (_info_changed_connection, invalidator (*this),
boost::bind (&PatchBankList::instrument_info_changed, this), gui_context());
if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
_route->processors_changed.connect (_route_connection, invalidator (*this),
boost::bind (&PatchBankList::processors_changed, this), gui_context());
}
}
PatchBankList::~PatchBankList ()
{
}
/* allow to sort bank-name by use-count */
template <typename A, typename B>
static std::multimap<B, A> flip_map (std::map<A, B> const& src)
{
std::multimap<B,A> dst;
for (typename std::map<A, B>::const_iterator it = src.begin(); it != src.end(); ++it) {
dst.insert (std::pair<B, A> (it->second, it->first));
}
return dst;
}
void
PatchBankList::refill (const uint8_t channel)
{
using namespace Menu_Helpers;
using namespace Gtkmm2ext;
using namespace MIDI::Name;
_current_patch_bank.reset ();
_bank_select.clear_items ();
const int b = bank (channel);
{
PBD::Unwinder<bool> uw (_ignore_spin_btn_signals, true);
_bank_msb_spin.set_value (b >> 7);
_bank_lsb_spin.set_value (b & 127);
}
typedef std::map<std::string, unsigned int> BankName;
typedef std::map<uint16_t, BankName> BankSet;
bool bank_set = false;
std::bitset<128> unset_notes;
BankSet generic_banks;
unset_notes.set ();
boost::shared_ptr<ChannelNameSet> cns = _info.get_patches (channel);
if (cns) {
const ChannelNameSet::PatchBanks& patch_banks = cns->patch_banks ();
for (ChannelNameSet::PatchBanks::const_iterator bank = patch_banks.begin (); bank != patch_banks.end (); ++bank) {
if ((*bank)->number () != UINT16_MAX) {
continue;
}
/* no shared MIDI bank for this PatchBanks,
* iterate over all programs in the patchbank, collect "<ControlChange>"
*/
const PatchNameList& patches = (*bank)->patch_name_list ();
for (PatchNameList::const_iterator patch = patches.begin (); patch != patches.end (); ++patch) {
BankName& bn (generic_banks[(*patch)->bank_number ()]);
++bn[(*bank)->name ()];
if ((*patch)->bank_number () != b) {
continue;
}
const std::string n = (*patch)->name ();
MIDI::Name::PatchPrimaryKey const& key = (*patch)->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)"),
Gtkmm2ext::markup_escape_text (n), (int)(pgm +1)));
unset_notes.reset (pgm);
}
}
for (ChannelNameSet::PatchBanks::const_iterator bank = patch_banks.begin (); bank != patch_banks.end (); ++bank) {
if ((*bank)->number () == UINT16_MAX) {
continue;
}
generic_banks.erase ((*bank)->number ());
std::string n = (*bank)->name ();
_bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchBankList::select_bank), (*bank)->number ())));
if ((*bank)->number () == b) {
_current_patch_bank = *bank;
_bank_select.set_text (n);
}
}
for (BankSet::const_iterator i = generic_banks.begin(); i != generic_banks.end(); ++i) {
std::string n = string_compose (_("Bank %1"), (i->first) + 1);
#if 1
typedef std::multimap <unsigned int, std::string> BankByCnt;
BankByCnt bc (flip_map (i->second));
unsigned int cnt = 0; // pick top three
for (BankByCnt::reverse_iterator j = bc.rbegin(); j != bc.rend() && cnt < 3; ++j, ++cnt) {
n += " (" + j->second + ")";
if (n.size () > 64) {
break;
}
}
if (bc.size () > cnt) {
n += " (...)";
}
#endif
_bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchBankList::select_bank), i->first)));
if (i->first == b) {
_bank_select.set_text (n);
bank_set = true;
}
}
}
if (!_current_patch_bank && !bank_set) {
std::string n = string_compose (_("Bank %1"), b + 1);
_bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchBankList::select_bank), b)));
_bank_select.set_text (n);
}
/* 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) {
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)"),
Gtkmm2ext::markup_escape_text (n), (int)(pgm +1)));
unset_notes.reset (pgm);
}
}
bool shade = unset_notes.count () != 128;
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);
if (shade) {
_program_btn[pgm].set_name (X_("patch change button unnamed"));
} else {
_program_btn[pgm].set_name (X_("patch change button"));
continue;
}
set_tooltip (_program_btn[pgm], n);
}
}
void
PatchBankList::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
PatchBankList::set_active_pgm (uint8_t p)
{
for (uint8_t pgm = 0; pgm < 128; ++pgm) {
_program_btn[pgm].set_active (pgm == p);
}
}
/* ****************************************************************************/
PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
: PatchBankList (r)
, _channel (-1)
, _no_notifications (false)
, _audition_enable (_("Audition on Change"), ArdourWidgets::ArdourButton::led_default_elements)
, _audition_start_spin (*manage (new Adjustment (48, 0, 127, 1, 16)))
, _audition_end_spin (*manage (new Adjustment (60, 0, 127, 1, 16)))
@ -97,15 +295,6 @@ PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
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 ());
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));
}
for (uint32_t chn = 0; chn < 16; ++chn) {
using namespace Menu_Helpers;
using namespace Gtkmm2ext;
@ -127,20 +316,13 @@ PatchChangeWidget::PatchChangeWidget (boost::shared_ptr<ARDOUR::Route> r)
_audition_enable.signal_clicked.connect (sigc::mem_fun (*this, &PatchChangeWidget::audition_toggle));
_audition_start_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), false));
_audition_end_spin.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::check_note_range), true));
_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());
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 ();
if (!boost::dynamic_pointer_cast<MidiTrack> (_route)) {
processors_changed ();
}
}
PatchChangeWidget::~PatchChangeWidget ()
@ -215,170 +397,16 @@ PatchChangeWidget::select_channel (uint8_t chn)
refill_banks ();
}
/* allow to sort bank-name by use-count */
template <typename A, typename B>
static std::multimap<B, A> flip_map (std::map<A, B> const& src)
{
std::multimap<B,A> dst;
for (typename std::map<A, B>::const_iterator it = src.begin(); it != src.end(); ++it) {
dst.insert (std::pair<B, A> (it->second, it->first));
}
return dst;
}
void
PatchChangeWidget::refill_banks ()
{
cancel_audition ();
using namespace Menu_Helpers;
using namespace Gtkmm2ext;
using namespace MIDI::Name;
_current_patch_bank.reset ();
_bank_select.clear_items ();
const int b = bank (_channel);
{
PBD::Unwinder<bool> uw (_ignore_spin_btn_signals, true);
_bank_msb_spin.set_value (b >> 7);
_bank_lsb_spin.set_value (b & 127);
}
typedef std::map<std::string, unsigned int> BankName;
typedef std::map<uint16_t, BankName> BankSet;
bool bank_set = false;
std::bitset<128> unset_notes;
BankSet generic_banks;
unset_notes.set ();
boost::shared_ptr<ChannelNameSet> cns = _info.get_patches (_channel);
if (cns) {
const ChannelNameSet::PatchBanks& patch_banks = cns->patch_banks ();
for (ChannelNameSet::PatchBanks::const_iterator bank = patch_banks.begin (); bank != patch_banks.end (); ++bank) {
if ((*bank)->number () != UINT16_MAX) {
continue;
}
/* no shared MIDI bank for this PatchBanks,
* iterate over all programs in the patchbank, collect "<ControlChange>"
*/
const PatchNameList& patches = (*bank)->patch_name_list ();
for (PatchNameList::const_iterator patch = patches.begin (); patch != patches.end (); ++patch) {
BankName& bn (generic_banks[(*patch)->bank_number ()]);
++bn[(*bank)->name ()];
if ((*patch)->bank_number () != b) {
continue;
}
const std::string n = (*patch)->name ();
MIDI::Name::PatchPrimaryKey const& key = (*patch)->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)"),
Gtkmm2ext::markup_escape_text (n), (int)(pgm +1)));
unset_notes.reset (pgm);
}
}
for (ChannelNameSet::PatchBanks::const_iterator bank = patch_banks.begin (); bank != patch_banks.end (); ++bank) {
if ((*bank)->number () == UINT16_MAX) {
continue;
}
generic_banks.erase ((*bank)->number ());
std::string n = (*bank)->name ();
_bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), (*bank)->number ())));
if ((*bank)->number () == b) {
_current_patch_bank = *bank;
_bank_select.set_text (n);
}
}
for (BankSet::const_iterator i = generic_banks.begin(); i != generic_banks.end(); ++i) {
std::string n = string_compose (_("Bank %1"), (i->first) + 1);
#if 1
typedef std::multimap <unsigned int, std::string> BankByCnt;
BankByCnt bc (flip_map (i->second));
unsigned int cnt = 0; // pick top three
for (BankByCnt::reverse_iterator j = bc.rbegin(); j != bc.rend() && cnt < 3; ++j, ++cnt) {
n += " (" + j->second + ")";
if (n.size () > 64) {
break;
}
}
if (bc.size () > cnt) {
n += " (...)";
}
#endif
_bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), i->first)));
if (i->first == b) {
_bank_select.set_text (n);
bank_set = true;
}
}
}
if (!_current_patch_bank && !bank_set) {
std::string n = string_compose (_("Bank %1"), b + 1);
_bank_select.AddMenuElem (MenuElemNoMnemonic (n, sigc::bind (sigc::mem_fun (*this, &PatchChangeWidget::select_bank), b)));
_bank_select.set_text (n);
}
/* 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) {
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)"),
Gtkmm2ext::markup_escape_text (n), (int)(pgm +1)));
unset_notes.reset (pgm);
}
}
bool shade = unset_notes.count () != 128;
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);
if (shade) {
_program_btn[pgm].set_name (X_("patch change button unnamed"));
} else {
_program_btn[pgm].set_name (X_("patch change button"));
continue;
}
set_tooltip (_program_btn[pgm], n);
}
refill (_channel);
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)
{
@ -450,9 +478,7 @@ void
PatchChangeWidget::program_changed ()
{
uint8_t p = program (_channel);
for (uint8_t pgm = 0; pgm < 128; ++pgm) {
_program_btn[pgm].set_active (pgm == p);
}
set_active_pgm (p);
}
void

View File

@ -34,7 +34,42 @@
#include "ardour_dialog.h"
#include "pianokeyboard.h"
class PatchChangeWidget : public Gtk::VBox
class PatchBankList : virtual public sigc::trackable
{
public:
PatchBankList (boost::shared_ptr<ARDOUR::Route>);
virtual ~PatchBankList ();
protected:
void refill (uint8_t const channel);
void set_active_pgm (uint8_t);
virtual int bank (uint8_t chn) const = 0;
virtual void select_bank (uint32_t) = 0;
virtual void select_program (uint8_t) = 0;
virtual void instrument_info_changed () = 0;
virtual void processors_changed () = 0;
boost::shared_ptr<ARDOUR::Route> _route;
ArdourWidgets::ArdourDropdown _bank_select;
Gtk::SpinButton _bank_msb_spin;
Gtk::SpinButton _bank_lsb_spin;
Gtk::Table _program_table;
private:
void select_bank_spin ();
ARDOUR::InstrumentInfo& _info;
ArdourWidgets::ArdourButton _program_btn[128];
boost::shared_ptr<MIDI::Name::PatchBank> _current_patch_bank;
bool _ignore_spin_btn_signals;
PBD::ScopedConnection _info_changed_connection;
PBD::ScopedConnection _route_connection;
};
class PatchChangeWidget : public Gtk::VBox, public PatchBankList
{
public:
PatchChangeWidget (boost::shared_ptr<ARDOUR::Route>);
@ -50,40 +85,29 @@ protected:
void on_hide ();
private:
boost::shared_ptr<ARDOUR::Route> _route;
void refill_banks ();
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;
bool _no_notifications;
void select_channel (uint8_t);
void select_bank (uint32_t);
void select_bank_spin ();
void select_program (uint8_t);
/* Implement PatchBankList */
void select_bank (uint32_t);
void select_program (uint8_t);
void instrument_info_changed ();
void processors_changed ();
/* callbacks from route AC */
void bank_changed ();
void program_changed ();
void bankpatch_changed (uint8_t);
void refill_banks ();
void instrument_info_changed ();
void processors_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;
/* Audition */
void audition_toggle ();
void check_note_range (bool);
void audition ();