View Issue Details

IDProjectCategoryView StatusLast Update
0001641ardourfeaturespublic2008-02-19 04:49
Reporterryan_scott Assigned To 
PrioritynormalSeverityfeatureReproducibilityN/A
Status newResolutionopen 
Summary0001641: Patch providing mechanism to automatically create midi control bindings based on mixer strip visibility
DescriptionThis patch automatically creates/deletes midi control bindings to mixer strips in the mixer window based on their visibility. In this way you can have whatever strip is in the 1st position mapped to the same physical midi control(s) no matter what location it is in in the track listing. Likewise for all other visible tracks.

The benefit is best illustrated using the example of a midi control surface which is configured like a traditional mixer. Ardour's mixer is always mapped to the corresponding physical surface.

Editing groups can be turned on and off as desired and midi mappings still conveniently utilized.
Additional InformationCurrently midi bindings are done sequentially (see the code for specifics). I would like to change this to involve the potential for specific mappings/information to be stored either in the session configuration or another external file.
TagsNo tags attached.

Activities

2007-05-01 01:16

 

ardour2_midi_auto_rebinding.patch (18,103 bytes)   
Index: gtk2_ardour/panner_ui.h
===================================================================
--- gtk2_ardour/panner_ui.h	(revision 1765)
+++ gtk2_ardour/panner_ui.h	(working copy)
@@ -68,9 +68,11 @@
 	void effective_pan_display ();
 
 	void set_meter_strip_name (string name);
+	PBD::Controllable* get_controllable();
 
   private:
 	friend class MixerStrip;
+
 	boost::shared_ptr<ARDOUR::IO> _io;
 	ARDOUR::Session& _session;
 
Index: gtk2_ardour/panner_ui.cc
===================================================================
--- gtk2_ardour/panner_ui.cc	(revision 1765)
+++ gtk2_ardour/panner_ui.cc	(working copy)
@@ -152,6 +152,12 @@
 	pan_automation_state_changed ();
 }
 
+PBD::Controllable* 
+PannerUI::get_controllable() 
+{ 
+	return pan_bars[0]->get_controllable();
+}
+
 bool
 PannerUI::panning_link_button_press (GdkEventButton* ev)
 {
Index: gtk2_ardour/mixer_ui.h
===================================================================
--- gtk2_ardour/mixer_ui.h	(revision 1765)
+++ gtk2_ardour/mixer_ui.h	(working copy)
@@ -75,6 +75,8 @@
 	void hide_strip (MixerStrip *);
 
 	void ensure_float (Gtk::Window&);
+	void toggle_auto_rebinding ();
+	void set_auto_rebinding(bool);
 
 	RouteRedirectSelection& selection() { return _selection; }
 	
@@ -121,6 +123,9 @@
 	void unselect_all_audiobus_strips ();
 	void select_all_audiobus_strips ();
 
+	void auto_rebind_midi_controls ();
+	bool auto_rebinding;
+
 	void strip_select_op (bool audiotrack, bool select);
 	void select_strip_op (MixerStrip*, bool select);
 
Index: gtk2_ardour/ardour.menus
===================================================================
--- gtk2_ardour/ardour.menus	(revision 1765)
+++ gtk2_ardour/ardour.menus	(working copy)
@@ -295,6 +295,8 @@
                    <menuitem action='RemoteUserDefined'/>
                    <menuitem action='RemoteMixerDefined'/>
                    <menuitem action='RemoteEditorDefined'/>
+    	           <separator/>
+                   <menuitem action='AutoRebinding'/>
                </menu>
                    <menu action='Monitoring'>
                    <menuitem action='UseHardwareMonitoring'/>
Index: gtk2_ardour/ardour_ui_ed.cc
===================================================================
--- gtk2_ardour/ardour_ui_ed.cc	(revision 1765)
+++ gtk2_ardour/ardour_ui_ed.cc	(working copy)
@@ -35,6 +35,7 @@
 #include "audio_clock.h"
 #include "editor.h"
 #include "actions.h"
+#include "mixer_ui.h"
 
 #include <ardour/session.h>
 #include <ardour/profile.h>
@@ -487,6 +488,10 @@
 	act = ActionManager::register_radio_action (option_actions, remote_group, X_("RemoteEditorDefined"), _("Remote ID follows order of Editor"), hide_return (bind (mem_fun (*this, &ARDOUR_UI::set_remote_model), EditorOrdered)));
 	ActionManager::session_sensitive_actions.push_back (act);
 
+	act = ActionManager::register_toggle_action (option_actions, X_("AutoRebinding"), _("Auto Rebind Controls"), mem_fun (*(this->mixer), &Mixer_UI::toggle_auto_rebinding));
+	ActionManager::session_sensitive_actions.push_back (act);
+
+
 	ActionManager::add_action_group (shuttle_actions);
 	ActionManager::add_action_group (option_actions);
 	ActionManager::add_action_group (jack_actions);
Index: gtk2_ardour/mixer_ui.cc
===================================================================
--- gtk2_ardour/mixer_ui.cc	(revision 1765)
+++ gtk2_ardour/mixer_ui.cc	(working copy)
@@ -219,6 +219,8 @@
 	signal_configure_event().connect (mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
 
 	_selection.RoutesChanged.connect (mem_fun(*this, &Mixer_UI::follow_strip_selection));
+
+	auto_rebinding = FALSE;
 }
 
 Mixer_UI::~Mixer_UI ()
@@ -634,8 +636,90 @@
 			}
 		}
 	}
+
+	// Rebind all of the midi controls automatically
+	
+	if (auto_rebinding)
+		auto_rebind_midi_controls ();
+
 }
 
+void
+Mixer_UI::set_auto_rebinding( bool val )
+{
+	if( val == TRUE )
+	{
+		auto_rebinding = TRUE;
+		Session::AutoBindingOff();
+	}
+	else
+	{
+		auto_rebinding = FALSE;
+		Session::AutoBindingOn();
+	}
+}
+
+void 
+Mixer_UI::toggle_auto_rebinding() 
+{
+	if (auto_rebinding)
+	{
+		set_auto_rebinding( FALSE );
+	}
+	
+	else
+	{
+		set_auto_rebinding( TRUE );
+	}
+
+	auto_rebind_midi_controls();
+}
+
+void 
+Mixer_UI::auto_rebind_midi_controls () 
+{
+	TreeModel::Children rows = track_model->children();
+	TreeModel::Children::iterator i;
+	int pos;
+
+	// Create bindings for all visible strips and remove those that are not visible
+	pos = 0;
+	for (i = rows.begin(); i != rows.end(); ++i) {
+		MixerStrip* strip = (*i)[track_columns.strip];
+    
+		if ( (*i)[track_columns.visible] == true ) {  // add bindings for
+			// make the actual binding
+			//cout<<"Auto Binding:  Visible Strip Found: "<<strip->name()<<endl;
+
+			PBD::Controllable::CreateBinding ( strip->solo_button->get_controllable(), pos, 0);
+			PBD::Controllable::CreateBinding ( strip->mute_button->get_controllable(), pos, 1);
+
+			if( strip->is_audio_track() ) {
+				PBD::Controllable::CreateBinding ( strip->rec_enable_button->get_controllable(), pos, 2);
+			}
+
+			PBD::Controllable::CreateBinding ( &(strip->gpm.get_controllable()), pos, 3);
+			PBD::Controllable::CreateBinding ( strip->panners.get_controllable(), pos, 4);
+
+			pos++;
+		}
+		else {  // Remove any existing binding
+			PBD::Controllable::DeleteBinding ( strip->solo_button->get_controllable() );
+			PBD::Controllable::DeleteBinding ( strip->mute_button->get_controllable() );
+
+			if( strip->is_audio_track() ) {
+				PBD::Controllable::DeleteBinding ( strip->rec_enable_button->get_controllable() );
+			}
+
+			PBD::Controllable::DeleteBinding ( &(strip->gpm.get_controllable()) );
+			PBD::Controllable::DeleteBinding ( strip->panners.get_controllable() ); // This only takes the first panner if there are multiples...
+		}
+
+	} // for
+  
+}
+
+
 struct SignalOrderRouteSorter {
     bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
 	    /* use of ">" forces the correct sort order */
@@ -991,6 +1075,7 @@
 	if (name != group->name()) {
 		group->set_name (name);
 	}
+
 }
 
 void
Index: gtk2_ardour/gain_meter.h
===================================================================
--- gtk2_ardour/gain_meter.h	(revision 1765)
+++ gtk2_ardour/gain_meter.h	(working copy)
@@ -73,6 +73,7 @@
 
 	void set_meter_strip_name (const char * name);
 	void set_fader_name (const char * name);
+	PBD::Controllable& get_controllable() { return _io->gain_control(); }
 
   private:
 
Index: libs/gtkmm2ext/gtkmm2ext/bindable_button.h
===================================================================
--- libs/gtkmm2ext/gtkmm2ext/bindable_button.h	(revision 1765)
+++ libs/gtkmm2ext/gtkmm2ext/bindable_button.h	(working copy)
@@ -47,7 +47,8 @@
 			return true;
 		}
 	}
-
+	
+	PBD::Controllable* get_controllable() { return binding_proxy.get_controllable(); }
   private:
 	BindingProxy binding_proxy;
 };
@@ -71,6 +72,8 @@
 		}
 	}
 
+	PBD::Controllable* get_controllable() { return binding_proxy.get_controllable(); }
+
   private:
 	BindingProxy binding_proxy;
 };
Index: libs/gtkmm2ext/gtkmm2ext/barcontroller.h
===================================================================
--- libs/gtkmm2ext/gtkmm2ext/barcontroller.h	(revision 1765)
+++ libs/gtkmm2ext/gtkmm2ext/barcontroller.h	(working copy)
@@ -59,6 +59,7 @@
 	/* export this to allow direct connection to button events */
 
 	Gtk::Widget& event_widget() { return darea; }
+	PBD::Controllable* get_controllable() { return binding_proxy.get_controllable(); }
 
   protected:
 	Gtk::Adjustment&    adjustment;
Index: libs/gtkmm2ext/gtkmm2ext/binding_proxy.h
===================================================================
--- libs/gtkmm2ext/gtkmm2ext/binding_proxy.h	(revision 1765)
+++ libs/gtkmm2ext/gtkmm2ext/binding_proxy.h	(working copy)
@@ -40,6 +40,7 @@
 
 	bool button_press_handler (GdkEventButton *);
 
+	PBD::Controllable* get_controllable() { return &controllable; }
   protected:
 
 	Gtkmm2ext::PopUp     prompter;
Index: libs/ardour/ardour/session.h
===================================================================
--- libs/ardour/ardour/session.h	(revision 1765)
+++ libs/ardour/ardour/session.h	(working copy)
@@ -247,6 +247,9 @@
 	bool deletion_in_progress() const { return _state_of_the_state & Deletion; }
 	sigc::signal<void> DirtyChanged;
 
+	static sigc::signal<void> AutoBindingOn;
+	static sigc::signal<void> AutoBindingOff;
+
 	std::string sound_dir (bool with_path = true) const;
 	std::string peak_dir () const;
 	std::string dead_sound_dir () const;
Index: libs/ardour/session.cc
===================================================================
--- libs/ardour/session.cc	(revision 1765)
+++ libs/ardour/session.cc	(working copy)
@@ -107,6 +107,10 @@
 sigc::signal<void> Session::StartTimeChanged;
 sigc::signal<void> Session::EndTimeChanged;
 
+sigc::signal<void> Session::AutoBindingOn;
+sigc::signal<void> Session::AutoBindingOff;
+
+
 int
 Session::find_session (string str, string& path, string& snapshot, bool& isnew)
 {
Index: libs/pbd/controllable.cc
===================================================================
--- libs/pbd/controllable.cc	(revision 1765)
+++ libs/pbd/controllable.cc	(working copy)
@@ -28,6 +28,8 @@
 sigc::signal<void,Controllable*> Controllable::Destroyed;
 sigc::signal<bool,Controllable*> Controllable::StartLearning;
 sigc::signal<void,Controllable*> Controllable::StopLearning;
+sigc::signal<void,Controllable*,int,int> Controllable::CreateBinding;
+sigc::signal<void,Controllable*> Controllable::DeleteBinding;
 
 Glib::Mutex* Controllable::registry_lock = 0;
 Controllable::Controllables Controllable::registry;
Index: libs/pbd/pbd/controllable.h
===================================================================
--- libs/pbd/pbd/controllable.h	(revision 1765)
+++ libs/pbd/pbd/controllable.h	(working copy)
@@ -43,6 +43,8 @@
 	virtual bool can_send_feedback() const { return true; }
 
 	sigc::signal<void> LearningFinished;
+	static sigc::signal<void,PBD::Controllable*,int,int> CreateBinding;
+	static sigc::signal<void,PBD::Controllable*> DeleteBinding;
 
 	static sigc::signal<bool,PBD::Controllable*> StartLearning;
 	static sigc::signal<void,PBD::Controllable*> StopLearning;
Index: libs/surfaces/tranzport/io_usb.cc
===================================================================
--- libs/surfaces/tranzport/io_usb.cc	(revision 1765)
+++ libs/surfaces/tranzport/io_usb.cc	(working copy)
@@ -137,7 +137,7 @@
 
 int TranzportControlProtocol::read(uint8_t *buf, uint32_t timeout_override) 
 {
-	last_read_error = usb_interrupt_read (udev, READ_ENDPOINT, (char *) buf, 8, timeout_override);
+  last_read_error = 0; //usb_interrupt_read (udev, READ_ENDPOINT, (char *) buf, 8, timeout_override);
 	switch(last_read_error) {
 	case -ENOENT:
 	case -ENXIO:
@@ -164,7 +164,7 @@
 {
 	int val;
 	if(inflight > MAX_TRANZPORT_INFLIGHT) { return (-1); }
-	val = usb_interrupt_write (udev, WRITE_ENDPOINT, (char*) cmd, 8, timeout_override ? timeout_override : timeout);
+	val = 0;//usb_interrupt_write (udev, WRITE_ENDPOINT, (char*) cmd, 8, timeout_override ? timeout_override : timeout);
 
 	if (val < 0 && val !=8) {
 #if DEBUG_TRANZPORT
Index: libs/surfaces/generic_midi/generic_midi_control_protocol.cc
===================================================================
--- libs/surfaces/generic_midi/generic_midi_control_protocol.cc	(revision 1765)
+++ libs/surfaces/generic_midi/generic_midi_control_protocol.cc	(working copy)
@@ -57,9 +57,17 @@
 	_feedback_interval = 10000; // microseconds
 	last_feedback_time = 0;
 
+	auto_binding = FALSE;
+
 	Controllable::StartLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::start_learning));
 	Controllable::StopLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::stop_learning));
 	Session::SendFeedback.connect (mem_fun (*this, &GenericMidiControlProtocol::send_feedback));
+	
+	Controllable::CreateBinding.connect (mem_fun (*this, &GenericMidiControlProtocol::create_binding));
+	Controllable::DeleteBinding.connect (mem_fun (*this, &GenericMidiControlProtocol::delete_binding));
+
+	Session::AutoBindingOn.connect (mem_fun (*this, &GenericMidiControlProtocol::auto_binding_on));
+	Session::AutoBindingOff.connect (mem_fun (*this, &GenericMidiControlProtocol::auto_binding_off));
 }
 
 GenericMidiControlProtocol::~GenericMidiControlProtocol ()
@@ -226,6 +234,71 @@
 	}
 }
 
+void
+GenericMidiControlProtocol::delete_binding ( PBD::Controllable* control )
+{
+	if( control != 0 ) {
+		Glib::Mutex::Lock lm2 (controllables_lock);
+		
+		for( MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
+			MIDIControllable* existingBinding = (*iter);
+			
+			if( control == &(existingBinding->get_controllable()) ) {
+				delete existingBinding;
+				controllables.erase (iter);
+			}
+			
+		} // end for midi controllables
+	} // end null check
+}
+void
+GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number)
+{
+	if( control != NULL ) {
+		Glib::Mutex::Lock lm2 (controllables_lock);
+		
+		MIDI::channel_t channel = (pos & 0xf);
+		MIDI::byte value = control_number;
+		
+		// Create a MIDIControllable::
+		MIDIControllable* mc = new MIDIControllable (*_port, *control);
+		
+		// Remove any old binding for this midi channel/type/value pair
+		// Note:  can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information
+		for( MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
+			MIDIControllable* existingBinding = (*iter);
+			
+			if( (existingBinding->get_control_channel() & 0xf ) == channel &&
+			    existingBinding->get_control_additional() == value &&
+			    (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller ) {
+				
+				delete existingBinding;
+				controllables.erase (iter);
+			}
+			
+		} // end for midi controllables
+		
+		
+		// Update the MIDI Controllable based on the the pos param
+		// Here is where a table lookup for user mappings could go; for now we'll just wing it...
+		mc->bind_midi( channel, MIDI::controller, value );
+		
+		controllables.insert (mc);
+	} // end null test
+}
+
+void
+GenericMidiControlProtocol::auto_binding_on()
+{
+	auto_binding = TRUE;
+}
+
+void
+GenericMidiControlProtocol::auto_binding_off()
+{
+	auto_binding = FALSE;
+}
+
 XMLNode&
 GenericMidiControlProtocol::get_state () 
 {
@@ -270,46 +343,47 @@
 		_feedback_interval = 10000;
 	}
 
-	Controllable* c;
+	// Are we using the autobinding feature?  If so skip this part
+	if ( !auto_binding ) {
+		
+		Controllable* c;
+		
+		{
+			Glib::Mutex::Lock lm (pending_lock);
+			pending_controllables.clear ();
+		}
+		
+		Glib::Mutex::Lock lm2 (controllables_lock);
+		controllables.clear ();
+		nlist = node.children(); // "controls"
+		
+		if (nlist.empty()) {
+			return 0;
+		}
+		
+		nlist = nlist.front()->children ();
+		
+		for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+			
+			if ((prop = (*niter)->property ("id")) != 0) {
 
-	{
-		Glib::Mutex::Lock lm (pending_lock);
-		pending_controllables.clear ();
-	}
-
-	Glib::Mutex::Lock lm2 (controllables_lock);
-
-	controllables.clear ();
-
-	nlist = node.children(); // "controls"
-
-	if (nlist.empty()) {
-		return 0;
-	}
-
-	nlist = nlist.front()->children ();
-
-	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
-
-		if ((prop = (*niter)->property ("id")) != 0) {
-			
-			ID id = prop->value ();
-			
-			c = Controllable::by_id (id);
-			
-			if (c) {
-				MIDIControllable* mc = new MIDIControllable (*_port, *c);
-				if (mc->set_state (**niter) == 0) {
-					controllables.insert (mc);
+				ID id = prop->value ();
+				c = session->controllable_by_id (id);
+				
+				if (c) {
+					MIDIControllable* mc = new MIDIControllable (*_port, *c);
+					if (mc->set_state (**niter) == 0) {
+						controllables.insert (mc);
+					}
+					
+				} else {
+					warning << string_compose (_("Generic MIDI control: controllable %1 not found in session (ignored)"),
+								   id)
+						<< endmsg;
 				}
-				
-			} else {
-				warning << string_compose (_("Generic MIDI control: controllable %1 not found (ignored)"), id)
-					<< endmsg;
 			}
 		}
-	}
-
+	} // end autobinding check
 	return 0;
 }
 
Index: libs/surfaces/generic_midi/generic_midi_control_protocol.h
===================================================================
--- libs/surfaces/generic_midi/generic_midi_control_protocol.h	(revision 1765)
+++ libs/surfaces/generic_midi/generic_midi_control_protocol.h	(working copy)
@@ -44,6 +44,7 @@
 	ARDOUR::microseconds_t last_feedback_time;
 
 	bool  do_feedback;
+	bool  auto_binding;
 	void _send_feedback ();
 	void  send_feedback ();
 
@@ -59,6 +60,13 @@
 	void stop_learning (PBD::Controllable*);
 
 	void learning_stopped (MIDIControllable*);
+
+	void create_binding (PBD::Controllable*, int, int);
+	void delete_binding (PBD::Controllable*);
+
+	void auto_binding_on();
+	void auto_binding_off();
+
 };
 
 #endif /* ardour_generic_midi_control_protocol_h */
Index: libs/surfaces/generic_midi/midicontrollable.h
===================================================================
--- libs/surfaces/generic_midi/midicontrollable.h	(revision 1765)
+++ libs/surfaces/generic_midi/midicontrollable.h	(working copy)
@@ -63,6 +63,10 @@
 	XMLNode& get_state (void);
 	int set_state (const XMLNode&);
 
+	void bind_midi (MIDI::channel_t, MIDI::eventType, MIDI::byte);
+	MIDI::channel_t get_control_channel () { return control_channel; }
+	MIDI::eventType get_control_type () { return control_type; }
+	MIDI::byte get_control_additional () { return control_additional; }
   private:
 	PBD::Controllable& controllable;
 	MIDI::Port&     _port;
@@ -86,8 +90,6 @@
 	void midi_sense_controller (MIDI::Parser &, MIDI::EventTwoBytes *);
 	void midi_sense_program_change (MIDI::Parser &, MIDI::byte);
 	void midi_sense_pitchbend (MIDI::Parser &, MIDI::pitchbend_t);
-
-	void bind_midi (MIDI::channel_t, MIDI::eventType, MIDI::byte);
 };
 
 #endif // __gm_midicontrollable_h__

paul

2007-05-02 15:02

administrator   ~0003871

ryan, thanks for this patch. on first look, it appears solid (and desirable).

i'm working on getting the MIDI branch to become trunk, after that, we can certainly consider applying this.

welcome!

2008-02-19 04:48

 

ardour2-ongoing-midi_rebinding.patch (18,505 bytes)   
=== gtk2_ardour/panner_ui.h
==================================================================
--- gtk2_ardour/panner_ui.h	(/ardour2_ongoing)	(revision 1324)
+++ gtk2_ardour/panner_ui.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -68,9 +68,11 @@
 	void effective_pan_display ();
 
 	void set_meter_strip_name (string name);
+	PBD::Controllable* get_controllable();
 
   private:
 	friend class MixerStrip;
+
 	boost::shared_ptr<ARDOUR::IO> _io;
 	ARDOUR::Session& _session;
 
=== gtk2_ardour/panner_ui.cc
==================================================================
--- gtk2_ardour/panner_ui.cc	(/ardour2_ongoing)	(revision 1324)
+++ gtk2_ardour/panner_ui.cc	(/ardour2/midi_rebinding)	(revision 1324)
@@ -152,6 +152,12 @@
 	pan_automation_state_changed ();
 }
 
+PBD::Controllable* 
+PannerUI::get_controllable() 
+{ 
+	return pan_bars[0]->get_controllable();
+}
+
 bool
 PannerUI::panning_link_button_press (GdkEventButton* ev)
 {
=== gtk2_ardour/mixer_ui.h
==================================================================
--- gtk2_ardour/mixer_ui.h	(/ardour2_ongoing)	(revision 1324)
+++ gtk2_ardour/mixer_ui.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -75,6 +75,8 @@
 	void hide_strip (MixerStrip *);
 
 	void ensure_float (Gtk::Window&);
+	void toggle_auto_rebinding ();
+	void set_auto_rebinding(bool);
 
 	RouteRedirectSelection& selection() { return _selection; }
 	
@@ -129,6 +131,9 @@
 	void unselect_all_audiobus_strips ();
 	void select_all_audiobus_strips ();
 
+	void auto_rebind_midi_controls ();
+	bool auto_rebinding;
+
 	void strip_select_op (bool audiotrack, bool select);
 	void select_strip_op (MixerStrip*, bool select);
 
=== gtk2_ardour/ardour.menus
==================================================================
--- gtk2_ardour/ardour.menus	(/ardour2_ongoing)	(revision 1324)
+++ gtk2_ardour/ardour.menus	(/ardour2/midi_rebinding)	(revision 1324)
@@ -364,6 +364,8 @@
                    <menuitem action='RemoteUserDefined'/>
                    <menuitem action='RemoteMixerDefined'/>
                    <menuitem action='RemoteEditorDefined'/>
+    	           <separator/>
+                   <menuitem action='AutoRebinding'/>
                </menu>
                <menu action='Monitoring'>
                    <menuitem action='UseHardwareMonitoring'/>
=== gtk2_ardour/ardour_ui_ed.cc
==================================================================
--- gtk2_ardour/ardour_ui_ed.cc	(/ardour2_ongoing)	(revision 1324)
+++ gtk2_ardour/ardour_ui_ed.cc	(/ardour2/midi_rebinding)	(revision 1324)
@@ -37,6 +37,7 @@
 #include "engine_dialog.h"
 #include "editor.h"
 #include "actions.h"
+#include "mixer_ui.h"
 
 #ifdef GTKOSX
 #include <gtkmm2ext/sync-menu.h>
@@ -571,6 +572,10 @@
 	act = ActionManager::register_radio_action (option_actions, remote_group, X_("RemoteEditorDefined"), _("Remote ID follows order of Editor"), hide_return (bind (mem_fun (*this, &ARDOUR_UI::set_remote_model), EditorOrdered)));
 	ActionManager::session_sensitive_actions.push_back (act);
 
+	act = ActionManager::register_toggle_action (option_actions, X_("AutoRebinding"), _("Auto Rebind Controls"), mem_fun (*(this->mixer), &Mixer_UI::toggle_auto_rebinding));
+	ActionManager::session_sensitive_actions.push_back (act);
+
+
 	ActionManager::add_action_group (shuttle_actions);
 	ActionManager::add_action_group (option_actions);
 	ActionManager::add_action_group (jack_actions);
=== gtk2_ardour/mixer_ui.cc
==================================================================
--- gtk2_ardour/mixer_ui.cc	(/ardour2_ongoing)	(revision 1324)
+++ gtk2_ardour/mixer_ui.cc	(/ardour2/midi_rebinding)	(revision 1324)
@@ -223,6 +223,8 @@
 	signal_configure_event().connect (mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
 
 	_selection.RoutesChanged.connect (mem_fun(*this, &Mixer_UI::follow_strip_selection));
+
+	auto_rebinding = FALSE;
 }
 
 Mixer_UI::~Mixer_UI ()
@@ -712,8 +714,97 @@
 		Route::SyncOrderKeys (); // EMIT SIGNAL
 		ignore_sync = false;
 	}
+
+	// Rebind all of the midi controls automatically
+	
+	if (auto_rebinding)
+		auto_rebind_midi_controls ();
+
 }
 
+void
+Mixer_UI::set_auto_rebinding( bool val )
+{
+	if( val == TRUE )
+	{
+		auto_rebinding = TRUE;
+		Session::AutoBindingOff();
+	}
+	else
+	{
+		auto_rebinding = FALSE;
+		Session::AutoBindingOn();
+	}
+}
+
+void 
+Mixer_UI::toggle_auto_rebinding() 
+{
+	if (auto_rebinding)
+	{
+		set_auto_rebinding( FALSE );
+	}
+	
+	else
+	{
+		set_auto_rebinding( TRUE );
+	}
+
+	auto_rebind_midi_controls();
+}
+
+void 
+Mixer_UI::auto_rebind_midi_controls () 
+{
+	TreeModel::Children rows = track_model->children();
+	TreeModel::Children::iterator i;
+	int pos;
+
+	// Create bindings for all visible strips and remove those that are not visible
+	pos = 1;  // 0 is reserved for the master strip
+	for (i = rows.begin(); i != rows.end(); ++i) {
+		MixerStrip* strip = (*i)[track_columns.strip];
+    
+		if ( (*i)[track_columns.visible] == true ) {  // add bindings for
+			// make the actual binding
+			//cout<<"Auto Binding:  Visible Strip Found: "<<strip->name()<<endl;
+
+			int controlValue = pos;
+			if( strip->route()->master() ) {
+				controlValue = 0;
+			}
+			else {
+				pos++;
+			}
+
+			PBD::Controllable::CreateBinding ( strip->solo_button->get_controllable(), controlValue, 0);
+			PBD::Controllable::CreateBinding ( strip->mute_button->get_controllable(), controlValue, 1);
+
+			if( strip->is_audio_track() ) {
+				PBD::Controllable::CreateBinding ( strip->rec_enable_button->get_controllable(), controlValue, 2);
+			}
+
+			PBD::Controllable::CreateBinding ( &(strip->gpm.get_controllable()), controlValue, 3);
+			PBD::Controllable::CreateBinding ( strip->panners.get_controllable(), controlValue, 4);
+
+		}
+		else {  // Remove any existing binding
+			PBD::Controllable::DeleteBinding ( strip->solo_button->get_controllable() );
+			PBD::Controllable::DeleteBinding ( strip->mute_button->get_controllable() );
+
+			if( strip->is_audio_track() ) {
+				PBD::Controllable::DeleteBinding ( strip->rec_enable_button->get_controllable() );
+			}
+
+			PBD::Controllable::DeleteBinding ( &(strip->gpm.get_controllable()) );
+			PBD::Controllable::DeleteBinding ( strip->panners.get_controllable() ); // This only takes the first panner if there are multiples...
+		}
+
+	} // for
+  
+}
+
+
 struct SignalOrderRouteSorter {
     bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
 	    /* use of ">" forces the correct sort order */
@@ -1069,6 +1160,7 @@
 	if (name != group->name()) {
 		group->set_name (name);
 	}
+
 }
 
 void
=== gtk2_ardour/gain_meter.h
==================================================================
--- gtk2_ardour/gain_meter.h	(/ardour2_ongoing)	(revision 1324)
+++ gtk2_ardour/gain_meter.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -73,6 +73,7 @@
 
 	void set_meter_strip_name (const char * name);
 	void set_fader_name (const char * name);
+	PBD::Controllable& get_controllable() { return _io->gain_control(); }
 
 	void clear_meters ();
 
=== libs/gtkmm2ext/gtkmm2ext/bindable_button.h
==================================================================
--- libs/gtkmm2ext/gtkmm2ext/bindable_button.h	(/ardour2_ongoing)	(revision 1324)
+++ libs/gtkmm2ext/gtkmm2ext/bindable_button.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -47,7 +47,8 @@
 			return true;
 		}
 	}
-
+	
+	PBD::Controllable* get_controllable() { return binding_proxy.get_controllable(); }
   private:
 	BindingProxy binding_proxy;
 };
@@ -71,6 +72,8 @@
 		}
 	}
 
+	PBD::Controllable* get_controllable() { return binding_proxy.get_controllable(); }
+
   private:
 	BindingProxy binding_proxy;
 };
=== libs/gtkmm2ext/gtkmm2ext/barcontroller.h
==================================================================
--- libs/gtkmm2ext/gtkmm2ext/barcontroller.h	(/ardour2_ongoing)	(revision 1324)
+++ libs/gtkmm2ext/gtkmm2ext/barcontroller.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -59,6 +59,7 @@
 	/* export this to allow direct connection to button events */
 
 	Gtk::Widget& event_widget() { return darea; }
+	PBD::Controllable* get_controllable() { return binding_proxy.get_controllable(); }
 
   protected:
 	Gtk::Adjustment&    adjustment;
=== libs/gtkmm2ext/gtkmm2ext/binding_proxy.h
==================================================================
--- libs/gtkmm2ext/gtkmm2ext/binding_proxy.h	(/ardour2_ongoing)	(revision 1324)
+++ libs/gtkmm2ext/gtkmm2ext/binding_proxy.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -40,6 +40,7 @@
 
 	bool button_press_handler (GdkEventButton *);
 
+	PBD::Controllable* get_controllable() { return &controllable; }
   protected:
 
 	Gtkmm2ext::PopUp*  prompter;
=== libs/ardour/ardour/session.h
==================================================================
--- libs/ardour/ardour/session.h	(/ardour2_ongoing)	(revision 1324)
+++ libs/ardour/ardour/session.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -248,6 +248,9 @@
 	bool deletion_in_progress() const { return _state_of_the_state & Deletion; }
 	sigc::signal<void> DirtyChanged;
 
+	static sigc::signal<void> AutoBindingOn;
+	static sigc::signal<void> AutoBindingOff;
+
 	std::string sound_dir (bool with_path = true) const;
 	std::string peak_dir () const;
 	std::string dead_sound_dir () const;
=== libs/ardour/session.cc
==================================================================
--- libs/ardour/session.cc	(/ardour2_ongoing)	(revision 1324)
+++ libs/ardour/session.cc	(/ardour2/midi_rebinding)	(revision 1324)
@@ -113,6 +113,10 @@
 sigc::signal<void> Session::StartTimeChanged;
 sigc::signal<void> Session::EndTimeChanged;
 
+sigc::signal<void> Session::AutoBindingOn;
+sigc::signal<void> Session::AutoBindingOff;
+
+
 int
 Session::find_session (string str, string& path, string& snapshot, bool& isnew)
 {
=== libs/pbd/controllable.cc
==================================================================
--- libs/pbd/controllable.cc	(/ardour2_ongoing)	(revision 1324)
+++ libs/pbd/controllable.cc	(/ardour2/midi_rebinding)	(revision 1324)
@@ -28,6 +28,8 @@
 sigc::signal<void,Controllable*> Controllable::Destroyed;
 sigc::signal<bool,Controllable*> Controllable::StartLearning;
 sigc::signal<void,Controllable*> Controllable::StopLearning;
+sigc::signal<void,Controllable*,int,int> Controllable::CreateBinding;
+sigc::signal<void,Controllable*> Controllable::DeleteBinding;
 
 Glib::Mutex* Controllable::registry_lock = 0;
 Controllable::Controllables Controllable::registry;
=== libs/pbd/pbd/controllable.h
==================================================================
--- libs/pbd/pbd/controllable.h	(/ardour2_ongoing)	(revision 1324)
+++ libs/pbd/pbd/controllable.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -44,6 +44,8 @@
 	virtual bool can_send_feedback() const { return true; }
 
 	sigc::signal<void> LearningFinished;
+	static sigc::signal<void,PBD::Controllable*,int,int> CreateBinding;
+	static sigc::signal<void,PBD::Controllable*> DeleteBinding;
 
 	static sigc::signal<bool,PBD::Controllable*> StartLearning;
 	static sigc::signal<void,PBD::Controllable*> StopLearning;
=== libs/surfaces/generic_midi/generic_midi_control_protocol.cc
==================================================================
--- libs/surfaces/generic_midi/generic_midi_control_protocol.cc	(/ardour2_ongoing)	(revision 1324)
+++ libs/surfaces/generic_midi/generic_midi_control_protocol.cc	(/ardour2/midi_rebinding)	(revision 1324)
@@ -56,9 +56,17 @@
 	_feedback_interval = 10000; // microseconds
 	last_feedback_time = 0;
 
+	auto_binding = FALSE;
+
 	Controllable::StartLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::start_learning));
 	Controllable::StopLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::stop_learning));
 	Session::SendFeedback.connect (mem_fun (*this, &GenericMidiControlProtocol::send_feedback));
+	
+	Controllable::CreateBinding.connect (mem_fun (*this, &GenericMidiControlProtocol::create_binding));
+	Controllable::DeleteBinding.connect (mem_fun (*this, &GenericMidiControlProtocol::delete_binding));
+
+	Session::AutoBindingOn.connect (mem_fun (*this, &GenericMidiControlProtocol::auto_binding_on));
+	Session::AutoBindingOff.connect (mem_fun (*this, &GenericMidiControlProtocol::auto_binding_off));
 }
 
 GenericMidiControlProtocol::~GenericMidiControlProtocol ()
@@ -225,6 +233,71 @@
 	}
 }
 
+void
+GenericMidiControlProtocol::delete_binding ( PBD::Controllable* control )
+{
+	if( control != 0 ) {
+		Glib::Mutex::Lock lm2 (controllables_lock);
+		
+		for( MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
+			MIDIControllable* existingBinding = (*iter);
+			
+			if( control == &(existingBinding->get_controllable()) ) {
+				delete existingBinding;
+				controllables.erase (iter);
+			}
+			
+		} // end for midi controllables
+	} // end null check
+}
+void
+GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, int control_number)
+{
+	if( control != NULL ) {
+		Glib::Mutex::Lock lm2 (controllables_lock);
+		
+		MIDI::channel_t channel = (pos & 0xf);
+		MIDI::byte value = control_number;
+		
+		// Create a MIDIControllable::
+		MIDIControllable* mc = new MIDIControllable (*_port, *control);
+		
+		// Remove any old binding for this midi channel/type/value pair
+		// Note:  can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information
+		for( MIDIControllables::iterator iter = controllables.begin(); iter != controllables.end(); ++iter) {
+			MIDIControllable* existingBinding = (*iter);
+			
+			if( (existingBinding->get_control_channel() & 0xf ) == channel &&
+			    existingBinding->get_control_additional() == value &&
+			    (existingBinding->get_control_type() & 0xf0 ) == MIDI::controller ) {
+				
+				delete existingBinding;
+				controllables.erase (iter);
+			}
+			
+		} // end for midi controllables
+		
+		
+		// Update the MIDI Controllable based on the the pos param
+		// Here is where a table lookup for user mappings could go; for now we'll just wing it...
+		mc->bind_midi( channel, MIDI::controller, value );
+		
+		controllables.insert (mc);
+	} // end null test
+}
+
+void
+GenericMidiControlProtocol::auto_binding_on()
+{
+	auto_binding = TRUE;
+}
+
+void
+GenericMidiControlProtocol::auto_binding_off()
+{
+	auto_binding = FALSE;
+}
+
 XMLNode&
 GenericMidiControlProtocol::get_state () 
 {
@@ -269,46 +342,47 @@
 		_feedback_interval = 10000;
 	}
 
-	Controllable* c;
+	// Are we using the autobinding feature?  If so skip this part
+	if ( !auto_binding ) {
+		
+		Controllable* c;
+		
+		{
+			Glib::Mutex::Lock lm (pending_lock);
+			pending_controllables.clear ();
+		}
+		
+		Glib::Mutex::Lock lm2 (controllables_lock);
+		controllables.clear ();
+		nlist = node.children(); // "controls"
+		
+		if (nlist.empty()) {
+			return 0;
+		}
+		
+		nlist = nlist.front()->children ();
+		
+		for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+			
+			if ((prop = (*niter)->property ("id")) != 0) {
 
-	{
-		Glib::Mutex::Lock lm (pending_lock);
-		pending_controllables.clear ();
-	}
-
-	Glib::Mutex::Lock lm2 (controllables_lock);
-
-	controllables.clear ();
-
-	nlist = node.children(); // "controls"
-
-	if (nlist.empty()) {
-		return 0;
-	}
-
-	nlist = nlist.front()->children ();
-
-	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
-
-		if ((prop = (*niter)->property ("id")) != 0) {
-			
-			ID id = prop->value ();
-			
-			c = Controllable::by_id (id);
-			
-			if (c) {
-				MIDIControllable* mc = new MIDIControllable (*_port, *c);
-				if (mc->set_state (**niter) == 0) {
-					controllables.insert (mc);
+				ID id = prop->value ();
+				c = session->controllable_by_id (id);
+				
+				if (c) {
+					MIDIControllable* mc = new MIDIControllable (*_port, *c);
+					if (mc->set_state (**niter) == 0) {
+						controllables.insert (mc);
+					}
+					
+				} else {
+					warning << string_compose (_("Generic MIDI control: controllable %1 not found in session (ignored)"),
+								   id)
+						<< endmsg;
 				}
-				
-			} else {
-				warning << string_compose (_("Generic MIDI control: controllable %1 not found (ignored)"), id)
-					<< endmsg;
 			}
 		}
-	}
-
+	} // end autobinding check
 	return 0;
 }
 
=== libs/surfaces/generic_midi/generic_midi_control_protocol.h
==================================================================
--- libs/surfaces/generic_midi/generic_midi_control_protocol.h	(/ardour2_ongoing)	(revision 1324)
+++ libs/surfaces/generic_midi/generic_midi_control_protocol.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -44,6 +44,7 @@
 	ARDOUR::microseconds_t last_feedback_time;
 
 	bool  do_feedback;
+	bool  auto_binding;
 	void _send_feedback ();
 	void  send_feedback ();
 
@@ -59,6 +60,13 @@
 	void stop_learning (PBD::Controllable*);
 
 	void learning_stopped (MIDIControllable*);
+
+	void create_binding (PBD::Controllable*, int, int);
+	void delete_binding (PBD::Controllable*);
+
+	void auto_binding_on();
+	void auto_binding_off();
+
 };
 
 #endif /* ardour_generic_midi_control_protocol_h */
=== libs/surfaces/generic_midi/midicontrollable.h
==================================================================
--- libs/surfaces/generic_midi/midicontrollable.h	(/ardour2_ongoing)	(revision 1324)
+++ libs/surfaces/generic_midi/midicontrollable.h	(/ardour2/midi_rebinding)	(revision 1324)
@@ -63,6 +63,10 @@
 	XMLNode& get_state (void);
 	int set_state (const XMLNode&);
 
+	void bind_midi (MIDI::channel_t, MIDI::eventType, MIDI::byte);
+	MIDI::channel_t get_control_channel () { return control_channel; }
+	MIDI::eventType get_control_type () { return control_type; }
+	MIDI::byte get_control_additional () { return control_additional; }
   private:
 	PBD::Controllable& controllable;
 	MIDI::Port&     _port;
@@ -86,8 +90,6 @@
 	void midi_sense_controller (MIDI::Parser &, MIDI::EventTwoBytes *);
 	void midi_sense_program_change (MIDI::Parser &, MIDI::byte);
 	void midi_sense_pitchbend (MIDI::Parser &, MIDI::pitchbend_t);
-
-	void bind_midi (MIDI::channel_t, MIDI::eventType, MIDI::byte);
 };
 
 #endif // __gm_midicontrollable_h__

Property changes on: 
___________________________________________________________________
Name: svk:merge
  64068436-a96e-4ecc-9bb0-3ea17f0254dd:/local/ardour2/branches/undo-serialization:20
  64068436-a96e-4ecc-9bb0-3ea17f0254dd:/local/undo:338
 +d708f5d6-7413-0410-9779-e7cbd77b26cf:/ardour2/branches/2.0-ongoing:3081
  d708f5d6-7413-0410-9779-e7cbd77b26cf:/ardour2/branches/undo:814

ryan_scott

2008-02-19 04:49

reporter   ~0004726

Uploaded latest patch. Fixed an ordering issue with the latest 2.0 codebase.

Issue History

Date Modified Username Field Change
2007-05-01 01:16 ryan_scott New Issue
2007-05-01 01:16 ryan_scott File Added: ardour2_midi_auto_rebinding.patch
2007-05-02 15:02 paul Note Added: 0003871
2008-02-19 04:48 ryan_scott File Added: ardour2-ongoing-midi_rebinding.patch
2008-02-19 04:49 ryan_scott Note Added: 0004726