kind-of-sort-of get MIDI automation editing working in cue editor/pianoroll

Still lots of details required here but the basic idea of displaying multiple lines and only
editing one is now operational
This commit is contained in:
Paul Davis
2024-12-20 10:08:20 -07:00
parent 228a4931e1
commit 8486c5ba98
4 changed files with 190 additions and 27 deletions

View File

@@ -130,11 +130,36 @@ MidiCueEditor::canvas_pre_event (GdkEvent* ev)
void
MidiCueEditor::build_lower_toolbar ()
{
velocity_button = new ArdourButton (_("Velocity"), ArdourButton::Text, true);
bender_button = new ArdourButton (_("Bender"), ArdourButton::Text, true);
pressure_button = new ArdourButton (_("Pressure"), ArdourButton::Text, true);
expression_button = new ArdourButton (_("Expression"), ArdourButton::Text, true);
modulation_button = new ArdourButton (_("Modulation"), ArdourButton::Text, true);
ArdourButton::Element elements = ArdourButton::Element (ArdourButton::Text|ArdourButton::Indicator|ArdourButton::Edge|ArdourButton::Body);
velocity_button = new ArdourButton (_("Velocity"), elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiVelocityAutomation), velocity_button));
bender_button = new ArdourButton (_("Bender"), elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiPitchBenderAutomation), bender_button));
pressure_button = new ArdourButton (_("Pressure"), elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiChannelPressureAutomation), pressure_button));
expression_button = new ArdourButton (_("Expression"), elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION), expression_button));
modulation_button = new ArdourButton (_("Modulation"), elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL), modulation_button));
cc_dropdown1 = new ArdourDropdown (elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_GENERAL_PURPOSE1), cc_dropdown1));
cc_dropdown2 = new ArdourDropdown (elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_GENERAL_PURPOSE2), cc_dropdown2));
cc_dropdown3 = new ArdourDropdown (elements);
parameter_button_map.insert (std::make_pair (Evoral::Parameter (ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_GENERAL_PURPOSE3), cc_dropdown3));
cc_dropdown1->set_text (string_compose (_("CC%1"), MIDI_CTL_MSB_GENERAL_PURPOSE1));
cc_dropdown2->set_text (string_compose (_("CC%1"), MIDI_CTL_MSB_GENERAL_PURPOSE2));
cc_dropdown3->set_text (string_compose (_("CC%1"), MIDI_CTL_MSB_GENERAL_PURPOSE3));
for (ParameterButtonMap::iterator i = parameter_button_map.begin(); i != parameter_button_map.end(); ++i) {
i->second->set_active_color (0xff0000ff);
i->second->set_distinct_led_click (true);
i->second->set_led_left (true);
i->second->set_act_on_release (false);
i->second->set_fallthrough_to_parent (true);
}
// button_bar.set_homogeneous (true);
button_bar.set_spacing (6);
@@ -143,12 +168,21 @@ MidiCueEditor::build_lower_toolbar ()
button_bar.pack_start (*bender_button, false, false);
button_bar.pack_start (*pressure_button, false, false);
button_bar.pack_start (*modulation_button, false, false);
button_bar.pack_start (*cc_dropdown1, false, false);
button_bar.pack_start (*cc_dropdown2, false, false);
button_bar.pack_start (*cc_dropdown3, false, false);
velocity_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiVelocityAutomation, 0), false);
pressure_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiChannelPressureAutomation, 0), false);
bender_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiPitchBenderAutomation, 0), false);
modulation_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL), false);
expression_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION), false);
velocity_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiVelocityAutomation, 0));
pressure_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiChannelPressureAutomation, 0));
bender_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiPitchBenderAutomation, 0));
modulation_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
expression_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
velocity_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_led_click), ARDOUR::MidiVelocityAutomation, 0));
pressure_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_led_click), ARDOUR::MidiChannelPressureAutomation, 0));
bender_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_led_click), ARDOUR::MidiPitchBenderAutomation, 0));
modulation_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_led_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
expression_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &MidiCueEditor::automation_led_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
_toolbox.pack_start (button_bar, false, false);
}
@@ -315,6 +349,7 @@ MidiCueEditor::build_canvas ()
prh = new ArdourCanvas::PianoRollHeader (v_scroll_group, *bg);
view = new MidiCueView (nullptr, *data_group, *no_scroll_group, *this, *bg, 0xff0000ff);
view->AutomationStateChange.connect (sigc::mem_fun (*this, &MidiCueEditor::automation_state_changed));
bg->set_view (view);
prh->set_view (view);
@@ -1871,12 +1906,24 @@ MidiCueEditor::set_region (std::shared_ptr<ARDOUR::MidiRegion> r)
bool
MidiCueEditor::automation_button_event (GdkEventButton* ev, Evoral::ParameterType type, int id)
{
if (ev->button != 1) {
return false;
}
SelectionOperation op = ArdourKeyboard::selection_type (ev->state);
switch (ev->type) {
case GDK_BUTTON_RELEASE:
automation_button_click (type, id, op);
break;
if (view) {
Evoral::Parameter param (type, 0, id);
if (view->is_visible_automation (param) && (op == SelectionSet)) {
op = SelectionToggle;
}
view->update_automation_display (param, op);
}
return true;
default:
break;
}
@@ -1885,11 +1932,41 @@ MidiCueEditor::automation_button_event (GdkEventButton* ev, Evoral::ParameterTyp
}
void
MidiCueEditor::automation_button_click (Evoral::ParameterType type, int id, SelectionOperation op)
MidiCueEditor::automation_led_click (GdkEventButton* ev, Evoral::ParameterType type, int id)
{
#warning paul allow channel selection (2nd param)
if (ev->button != 1) {
return;
}
if (view) {
view->update_automation_display (Evoral::Parameter (type, 0, id), op);
view->set_active_automation (Evoral::Parameter (type, 0, id));
}
}
void
MidiCueEditor::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->first));
/* Indicate visible automation state with selected/not-selected visual state */
if (view->is_visible_automation (i->first)) {
i->second->set_visual_state (Gtkmm2ext::Selected);
} else {
i->second->set_visual_state (Gtkmm2ext::NoVisualState);
}
/* Indicate active automation state with explicit widget active state (LED) */
if (view->is_active_automation (i->first)) {
i->second->set_active_state (Gtkmm2ext::ExplicitActive);
} else {
i->second->set_active_state (Gtkmm2ext::Off);
}
}
}

View File

@@ -18,6 +18,8 @@
#pragma once
#include <map>
#include <gtkmm/adjustment.h>
#include "canvas/ruler.h"
@@ -189,6 +191,12 @@ class MidiCueEditor : public CueEditor
ArdourWidgets::ArdourButton* pressure_button;
ArdourWidgets::ArdourButton* expression_button;
ArdourWidgets::ArdourButton* modulation_button;
ArdourWidgets::ArdourDropdown* cc_dropdown1;
ArdourWidgets::ArdourDropdown* cc_dropdown2;
ArdourWidgets::ArdourDropdown* cc_dropdown3;
typedef std::map<Evoral::Parameter,ArdourWidgets::ArdourButton*> ParameterButtonMap;
ParameterButtonMap parameter_button_map;
CueMidiBackground* bg;
MidiCueView* view;
@@ -244,12 +252,15 @@ class MidiCueEditor : public CueEditor
samplecnt_t data_capture_duration;
bool automation_button_event (GdkEventButton*, Evoral::ParameterType type, int id);
void automation_button_click (Evoral::ParameterType type, int id, ARDOUR::SelectionOperation);
bool automation_button_click (Evoral::ParameterType type, int id, ARDOUR::SelectionOperation);
void automation_led_click (GdkEventButton*, Evoral::ParameterType type, int id);
int _visible_channel;
ARDOUR::NoteMode _note_mode;
sigc::signal<void> NoteModeChanged;
void automation_state_changed ();
};

View File

@@ -303,6 +303,8 @@ MidiCueView::update_automation_display (Evoral::Parameter const & param, Selecti
automation_group,
ac->alist(),
ac->desc()));
line->set_line_color ("midi line inactive");
AutomationDisplayState cad (ac, line, true);
auto res = automation_map.insert (std::make_pair (param, cad));
@@ -311,25 +313,26 @@ MidiCueView::update_automation_display (Evoral::Parameter const & param, Selecti
}
}
std::cerr << "sad " << op << " param " << ARDOUR::EventTypeMap::instance().to_symbol (param) << std::endl;
switch (op) {
case SelectionSet:
/* hide the rest */
for (auto & as : automation_map) {
as.second.hide ();
}
/*FALLTHRU*/
ads->set_height (automation_group->get().height());
ads->show ();
internal_set_active_automation (param);
break;
case SelectionAdd:
ads->set_height (automation_group->get().height());
ads->show ();
active_automation = ads;
break;
case SelectionRemove:
ads->hide ();
if (active_automation == ads) {
active_automation = nullptr;
unset_active_automation ();
}
break;
@@ -337,14 +340,14 @@ MidiCueView::update_automation_display (Evoral::Parameter const & param, Selecti
if (ads->visible) {
ads->hide ();
if (active_automation == ads) {
active_automation = nullptr;
unset_active_automation ();
}
} else {
ads->set_height (automation_group->get().height());
ads->show ();
active_automation = ads;
internal_set_active_automation (param);
}
return;
break;
case SelectionExtend:
/* undefined in this context */
@@ -354,6 +357,72 @@ MidiCueView::update_automation_display (Evoral::Parameter const & param, Selecti
set_height (_height);
}
void
MidiCueView::set_active_automation (Evoral::Parameter const & param)
{
if (!internal_set_active_automation (param)) {
update_automation_display (param, SelectionSet);
}
}
void
MidiCueView::unset_active_automation ()
{
for (CueAutomationMap::iterator i = automation_map.begin(); i != automation_map.end(); ++i) {
i->second.line->set_line_color ("midi line inactive");
}
active_automation = nullptr;
AutomationStateChange(); /* EMIT SIGNAL */
}
bool
MidiCueView::internal_set_active_automation (Evoral::Parameter const & param)
{
bool exists = false;
for (CueAutomationMap::iterator i = automation_map.begin(); i != automation_map.end(); ++i) {
if (i->first == param) {
i->second.line->set_line_color ("gain line");
active_automation = &i->second;
exists = true;
} else {
i->second.line->set_line_color ("midi line inactive");
}
}
if (exists) {
AutomationStateChange(); /* EMIT SIGNAL */
}
return exists;
}
bool
MidiCueView::is_active_automation (Evoral::Parameter const & param) const
{
CueAutomationMap::const_iterator i = automation_map.find (param);
if (i == automation_map.end()) {
return false;
}
return (&i->second == active_automation);
}
bool
MidiCueView::is_visible_automation (Evoral::Parameter const & param) const
{
CueAutomationMap::const_iterator i = automation_map.find (param);
if (i == automation_map.end()) {
return false;
}
return (i->second.visible);
}
std::list<SelectableOwner*>
MidiCueView::selectable_owners()
{
@@ -368,6 +437,8 @@ MergeableLine*
MidiCueView::make_merger ()
{
if (active_automation && active_automation->line) {
std::cerr << "Mergable will use active automation @ " << active_automation << std::endl;
return new MergeableLine (active_automation->line, active_automation->control,
[](Temporal::timepos_t const& t) { return t; },
nullptr, nullptr);
@@ -402,10 +473,8 @@ void
MidiCueView::AutomationDisplayState::hide ()
{
if (velocity_display) {
std::cerr << "hide vdisp\n";
velocity_display->hide ();
} else if (line) {
std::cerr << "hide line\n";
line->hide_all ();
}
visible = false;
@@ -415,10 +484,8 @@ void
MidiCueView::AutomationDisplayState::show ()
{
if (velocity_display) {
std::cerr << "show vdisp\n";
velocity_display->show ();
} else if (line) {
std::cerr << "show line\n";
line->show ();
}
visible = true;

View File

@@ -61,6 +61,9 @@ class MidiCueView : public MidiView
void ghost_sync_selection (NoteBase*);
void update_automation_display (Evoral::Parameter const & param, ARDOUR::SelectionOperation);
void set_active_automation (Evoral::Parameter const &);
bool is_active_automation (Evoral::Parameter const &) const;
bool is_visible_automation (Evoral::Parameter const &) const;
ArdourCanvas::Item* drag_group() const;
@@ -73,6 +76,8 @@ class MidiCueView : public MidiView
void automation_entry();
void automation_leave ();
sigc::signal<void> AutomationStateChange;
protected:
bool scroll (GdkEventScroll* ev);
@@ -114,4 +119,7 @@ class MidiCueView : public MidiView
void update_hit (Hit *);
double _height;
bool internal_set_active_automation (Evoral::Parameter const &);
void unset_active_automation ();
};