redesign bottom of pianoroll (automation/MIDI CC buttons)

This should hopefully disambiguate where to click and why
This commit is contained in:
Paul Davis 2026-01-05 16:23:12 -07:00
parent 7e9c73b807
commit f3e581be0d
2 changed files with 161 additions and 85 deletions

View file

@ -136,6 +136,8 @@ Pianoroll::rebuild_parameter_button_map()
void
Pianoroll::reset_user_cc_choice (std::string name, Evoral::Parameter param, MetaButton* metabutton)
{
#ifdef PIANOROLL_USER_BUTTONS
EC_LOCAL_TEMPO_SCOPE;
ParameterButtonMap::iterator iter;
@ -150,6 +152,7 @@ Pianoroll::reset_user_cc_choice (std::string name, Evoral::Parameter param, Meta
parameter_button_map.insert (std::make_pair (metabutton, param));
metabutton->set_by_menutext (name);
#endif
}
void
@ -185,6 +188,8 @@ Pianoroll::add_multi_controller_item (Gtk::Menu_Helpers::MenuList&,
const std::string& name,
MetaButton* mb)
{
#ifdef PIANOROLL_USER_BUTTONS
EC_LOCAL_TEMPO_SCOPE;
using namespace Gtk;
@ -231,22 +236,22 @@ Pianoroll::add_multi_controller_item (Gtk::Menu_Helpers::MenuList&,
*/
mb->add_item (name, menu_text, *chn_menu, [](){});
#endif
}
void
Pianoroll::build_lower_toolbar ()
{
EC_LOCAL_TEMPO_SCOPE;
Gtk::RadioButtonGroup edit_group;
horizontal_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &Pianoroll::scrolled));
ArdourButton::Element elements = ArdourButton::Element (ArdourButton::Text|ArdourButton::Indicator|ArdourButton::Edge|ArdourButton::Body);
velocity_button = new ArdourButton (_("Velocity"), elements);
bender_button = new ArdourButton (_("Bender"), elements);
pressure_button = new ArdourButton (_("Pressure"), elements);
expression_button = new ArdourButton (_("Expression"), elements);
modulation_button = new ArdourButton (_("Modulation"), elements);
velocity_button = new ControllerControls (-1, _("Velocity"), edit_group);
bender_button = new ControllerControls (MIDI_CMD_BENDER, _("Bender"), edit_group);
pressure_button = new ControllerControls (MIDI_CMD_CHANNEL_PRESSURE, _("Pressure"), edit_group);
expression_button = new ControllerControls (MIDI_CTL_MSB_EXPRESSION, _("Expression"), edit_group);
modulation_button = new ControllerControls (MIDI_CTL_MSB_MODWHEEL, _("Modulation"), edit_group);
#ifdef PIANOROLL_USER_BUTTONS
cc_dropdown1 = new MetaButton ();
@ -263,18 +268,6 @@ Pianoroll::build_lower_toolbar ()
#endif
rebuild_parameter_button_map ();
/* Only need to do this once because i->first is the actual button,
* which does not change even when the parameter_button_map is rebuilt.
*/
for (ParameterButtonMap::iterator i = parameter_button_map.begin(); i != parameter_button_map.end(); ++i) {
i->first->set_active_color (0xff0000ff);
i->first->set_distinct_led_click (true);
i->first->set_led_left (true);
i->first->set_act_on_release (false);
i->first->set_fallthrough_to_parent (true);
}
// button_bar.set_homogeneous (true);
button_bar.set_spacing (6);
button_bar.set_border_width (6);
@ -289,26 +282,26 @@ Pianoroll::build_lower_toolbar ()
button_bar.pack_start (*cc_dropdown3, false, false);
#endif
velocity_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiVelocityAutomation, 0));
pressure_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiChannelPressureAutomation, 0));
bender_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiPitchBenderAutomation, 0));
modulation_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
expression_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
velocity_button->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_show_button_click), ARDOUR::MidiVelocityAutomation, 0));
pressure_button->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_show_button_click), ARDOUR::MidiChannelPressureAutomation, 0));
bender_button->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_show_button_click), ARDOUR::MidiPitchBenderAutomation, 0));
modulation_button->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_show_button_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
expression_button->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_show_button_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
velocity_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiVelocityAutomation, 0));
pressure_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiChannelPressureAutomation, 0));
bender_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiPitchBenderAutomation, 0));
modulation_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
expression_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
velocity_button->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_active_button_click), ARDOUR::MidiVelocityAutomation, 0));
pressure_button->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_active_button_click), ARDOUR::MidiChannelPressureAutomation, 0));
bender_button->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_active_button_click), ARDOUR::MidiPitchBenderAutomation, 0));
modulation_button->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_active_button_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
expression_button->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_active_button_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
#ifdef PIANOROLL_USER_BUTTONS
cc_dropdown1->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_button_event), cc_dropdown1), false);
cc_dropdown2->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_button_event), cc_dropdown2), false);
cc_dropdown3->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_button_event), cc_dropdown3), false);
cc_dropdown1->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_active_button_click), cc_dropdown1), false);
cc_dropdown2->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_active_button_click), cc_dropdown2), false);
cc_dropdown3->show_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_active_button_click), cc_dropdown3), false);
cc_dropdown1->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown1));
cc_dropdown2->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown2));
cc_dropdown3->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown3));
cc_dropdown1->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown1));
cc_dropdown2->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown2));
cc_dropdown3->edit_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown3));
cc_dropdown1->signal_map().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::build_cc_menu), cc_dropdown1));
cc_dropdown2->signal_map().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::build_cc_menu), cc_dropdown2));
@ -1483,8 +1476,9 @@ Pianoroll::set_region (std::shared_ptr<ARDOUR::Region> region)
}
bool
Pianoroll::user_automation_button_event (GdkEventButton* ev, MetaButton* mb)
Pianoroll::user_automation_active_button_click (GdkEventButton* ev, MetaButton* mb)
{
#ifdef PIANOROLL_USER_BUTTONS
EC_LOCAL_TEMPO_SCOPE;
if (mb->is_menu_popup_event (ev)) {
@ -1505,12 +1499,14 @@ Pianoroll::user_automation_button_event (GdkEventButton* ev, MetaButton* mb)
view->set_active_automation (i->second);
}
#endif
return true;
}
void
Pianoroll::user_led_click (GdkEventButton* ev, MetaButton* metabutton)
Pianoroll::user_automation_show_button_click (GdkEventButton* ev, MetaButton* metabutton)
{
#ifdef PIANOROLL_USER_BUTTONS
EC_LOCAL_TEMPO_SCOPE;
if (ev->button != 1) {
@ -1523,43 +1519,31 @@ Pianoroll::user_led_click (GdkEventButton* ev, MetaButton* metabutton)
return;
}
automation_button_event (ev, i->second.type(), i->second.id());
}
bool
Pianoroll::automation_button_event (GdkEventButton* ev, Evoral::ParameterType type, int id)
{
EC_LOCAL_TEMPO_SCOPE;
if (ev->button != 1) {
return false;
}
if (view) {
view->set_active_automation (Evoral::Parameter (type, _visible_channel, id));
}
return true;
automation_active_button_click (ev, i->second.type(), i->second.id());
#endif
}
void
Pianoroll::automation_led_click (GdkEventButton* ev, Evoral::ParameterType type, int id)
Pianoroll::automation_active_button_click (Evoral::ParameterType type, int id)
{
EC_LOCAL_TEMPO_SCOPE;
if (ev->button != 1) {
return;
if (view) {
Evoral::Parameter p (type, id);
std::string str (ARDOUR::EventTypeMap::instance().to_symbol (p));
std::cerr << "set active for " << str << std::endl;
view->set_active_automation (Evoral::Parameter (type, _visible_channel, id));
}
}
switch (ev->type) {
case GDK_BUTTON_RELEASE:
if (view) {
Evoral::Parameter param (type, _visible_channel, id);
view->toggle_visibility (param);
}
break;
default:
break;
void
Pianoroll::automation_show_button_click (Evoral::ParameterType type, int id)
{
EC_LOCAL_TEMPO_SCOPE;
if (view) {
Evoral::Parameter param (type, _visible_channel, id);
view->toggle_visibility (param);
}
}
@ -1570,23 +1554,24 @@ Pianoroll::automation_state_changed ()
assert (view);
for (ParameterButtonMap::iterator i = parameter_button_map.begin(); i != parameter_button_map.end(); ++i) {
std::string str (ARDOUR::EventTypeMap::instance().to_symbol (i->second));
/* Indicate active automation state with selected/not-selected visual state */
if (view->is_active_automation (i->second)) {
i->first->set_visual_state (Gtkmm2ext::Selected);
i->first->set_editing (true);
} else {
i->first->set_visual_state (Gtkmm2ext::NoVisualState);
i->first->set_editing (false);
}
/* Indicate visible automation state with explicit widget active state (LED) */
if (view->is_visible_automation (i->second)) {
i->first->set_active_state (Gtkmm2ext::ExplicitActive);
i->first->set_showing (true);
} else {
i->first->set_active_state (Gtkmm2ext::Off);
i->first->set_showing (false);
}
}
}
@ -2137,3 +2122,73 @@ Pianoroll::midiviews_from_region_selection (RegionSelection const &) const
return mv;
}
/*----*/
using namespace ArdourWidgets;
ControllerControls::ControllerControls (int num, std::string const & str, Gtk::RadioButtonGroup& group)
: number (num)
{
using namespace Gtk;
ArdourButton::Element elements = ArdourButton::Element (ArdourButton::VectorIcon|ArdourButton::Edge|ArdourButton::Body);
show_hide_button = new ArdourButton (elements);
edit_button = new ArdourButton (elements);
name.set_text (str);
show_hide_button->set_icon (ArdourIcon::HideEye);
edit_button->set_icon (ArdourIcon::ToolDraw);
show_hide_button->signal_clicked.connect (sigc::mem_fun (show_clicked, &sigc::signal<void>::emit));
edit_button->signal_clicked.connect (sigc::mem_fun (edit_clicked, &sigc::signal<void>::emit));
show_hide_button->set_active_color (0xff0000ff);
show_hide_button->set_act_on_release (false);
show_hide_button->set_fallthrough_to_parent (false);
edit_button->set_active_color (0xff0000ff);
edit_button->set_act_on_release (false);
edit_button->set_fallthrough_to_parent (false);
set_spacing (6);
set_border_width (12);
pack_start (*show_hide_button, false, false);
pack_start (*edit_button, false, false);
pack_start (name, false, false, 6);
show_all ();
}
ControllerControls::~ControllerControls ()
{
}
bool
ControllerControls::showing() const
{
return show_hide_button->active_state() != Gtkmm2ext::Off;
}
bool
ControllerControls::editing() const
{
return edit_button->get_active();
}
void
ControllerControls::set_showing (bool yn)
{
show_hide_button->set_active_state (yn ? Gtkmm2ext::ExplicitActive : Gtkmm2ext::Off);
if (!yn) {
show_hide_button->set_icon (ArdourIcon::EditorShowAutoOnTouch);
} else {
show_hide_button->set_icon (ArdourIcon::HideEye);
}
}
void
ControllerControls::set_editing (bool yn)
{
edit_button->set_active_state (yn ? Gtkmm2ext::ExplicitActive : Gtkmm2ext::Off);
}

View file

@ -23,6 +23,7 @@
#include "pbd/timer.h"
#include <ytkmm/adjustment.h>
#include <ytkmm/radiotoolbutton.h>
#include "canvas/ruler.h"
#include "widgets/eventboxext.h"
@ -52,6 +53,25 @@ namespace ArdourWidgets {
class PianorollMidiView;
class PianorollMidiBackground;
struct ControllerControls : public Gtk::HBox {
ControllerControls (int num, std::string const & name, Gtk::RadioButtonGroup& group);
~ControllerControls();
ArdourWidgets::ArdourButton* show_hide_button;
ArdourWidgets::ArdourButton* edit_button;
Gtk::Label name;
int number;
bool showing() const;
bool editing() const;
sigc::signal<void> show_clicked;
sigc::signal<void> edit_clicked;
void set_showing (bool);
void set_editing (bool);
};
class Pianoroll : public CueEditor
{
public:
@ -156,16 +176,17 @@ class Pianoroll : public CueEditor
ArdourCanvas::Rectangle* meter_bar;
ArdourCanvas::PianoRollHeader* prh;
ArdourWidgets::ArdourButton* velocity_button;
ArdourWidgets::ArdourButton* bender_button;
ArdourWidgets::ArdourButton* pressure_button;
ArdourWidgets::ArdourButton* expression_button;
ArdourWidgets::ArdourButton* modulation_button;
ArdourWidgets::MetaButton* cc_dropdown1;
ArdourWidgets::MetaButton* cc_dropdown2;
ArdourWidgets::MetaButton* cc_dropdown3;
typedef std::map<ArdourWidgets::ArdourButton*,Evoral::Parameter> ParameterButtonMap;
ControllerControls* velocity_button;
ControllerControls* bender_button;
ControllerControls* pressure_button;
ControllerControls* expression_button;
ControllerControls* modulation_button;
#ifdef PIANOROLL_USER_BUTTONS
ControllerControls cc_dropdown1;
ControllerControls cc_dropdown2;
ControllerControls cc_dropdown3;
#endif
typedef std::map<ControllerControls*,Evoral::Parameter> ParameterButtonMap;
ParameterButtonMap parameter_button_map;
void rebuild_parameter_button_map ();
@ -205,11 +226,11 @@ class Pianoroll : public CueEditor
bool idle_data_captured ();
bool user_automation_button_event (GdkEventButton* ev, ArdourWidgets::MetaButton* mb);
bool automation_button_event (GdkEventButton*, Evoral::ParameterType type, int id);
bool automation_button_click (Evoral::ParameterType type, int id, ARDOUR::SelectionOperation);
void automation_led_click (GdkEventButton*, Evoral::ParameterType type, int id);
void user_led_click (GdkEventButton* ev, ArdourWidgets::MetaButton* metabutton);
bool user_automation_active_button_click (GdkEventButton* ev, ArdourWidgets::MetaButton* mb);
void user_automation_show_button_click (GdkEventButton* ev, ArdourWidgets::MetaButton* metabutton);
void automation_active_button_click (Evoral::ParameterType type, int id);
void automation_show_button_click (Evoral::ParameterType type, int id);
int _visible_channel;