OSC: take care of track order changes too. Deal with mixer topology changes one time.
[ardour.git] / libs / surfaces / osc / osc.cc
index ff5c093758be5819ce81703f8336b188cf709147..19c8a3396530856b7a90d9e61f36ab436c38eeca 100644 (file)
 #include "ardour/plugin_insert.h"
 #include "ardour/presentation_info.h"
 #include "ardour/send.h"
+#include "ardour/phase_control.h"
+#include "ardour/solo_isolate_control.h"
+#include "ardour/solo_safe_control.h"
+#include "ardour/vca_manager.h"
 
 #include "osc_select_observer.h"
 #include "osc.h"
@@ -87,6 +91,8 @@ OSC::OSC (Session& s, uint32_t port)
        , _osc_unix_server (0)
        , _send_route_changes (true)
        , _debugmode (Off)
+       , tick (true)
+       , bank_dirty (false)
        , gui (0)
 {
        _instance = this;
@@ -243,8 +249,18 @@ OSC::start ()
        periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &OSC::periodic));
        periodic_timeout->attach (main_loop()->get_context());
 
+       // catch GUI select changes for GUI_select mode
        StripableSelectionChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&OSC::gui_selection_changed, this, _1), this);
 
+       // catch track reordering
+       // receive routes added
+       session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSC::notify_routes_added, this, _1), this);
+       // receive VCAs added
+       session->vca_manager().VCAAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSC::notify_vca_added, this, _1), this);
+       // order changed
+       PresentationInfo::Change.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&OSC::recalcbanks, this), this);
+
+
        return 0;
 }
 
@@ -518,25 +534,34 @@ OSC::register_callbacks()
                REGISTER_CALLBACK (serv, "/select/record_safe", "i", sel_recsafe);
                REGISTER_CALLBACK (serv, "/select/mute", "i", sel_mute);
                REGISTER_CALLBACK (serv, "/select/solo", "i", sel_solo);
+               REGISTER_CALLBACK (serv, "/select/solo_iso", "i", sel_solo_iso);
+               REGISTER_CALLBACK (serv, "/select/solo_safe", "i", sel_solo_safe);
                REGISTER_CALLBACK (serv, "/select/monitor_input", "i", sel_monitor_input);
                REGISTER_CALLBACK (serv, "/select/monitor_disk", "i", sel_monitor_disk);
+               REGISTER_CALLBACK (serv, "/select/polarity", "i", sel_phase);
                REGISTER_CALLBACK (serv, "/select/gain", "f", sel_gain);
                REGISTER_CALLBACK (serv, "/select/fader", "f", sel_fader);
                REGISTER_CALLBACK (serv, "/select/trimdB", "f", sel_trim);
                REGISTER_CALLBACK (serv, "/select/pan_stereo_position", "f", sel_pan_position);
+               REGISTER_CALLBACK (serv, "/select/pan_stereo_width", "f", sel_pan_width);
+               REGISTER_CALLBACK (serv, "/select/send_gain", "if", sel_sendgain);
+               REGISTER_CALLBACK (serv, "/select/send_fader", "if", sel_sendfader);
+               REGISTER_CALLBACK (serv, "/select/send_enable", "if", sel_sendenable);
 
                /* These commands require the route index in addition to the arg; TouchOSC (et al) can't use these  */ 
                REGISTER_CALLBACK (serv, "/strip/mute", "ii", route_mute);
                REGISTER_CALLBACK (serv, "/strip/solo", "ii", route_solo);
+               REGISTER_CALLBACK (serv, "/strip/solo_iso", "ii", route_solo_iso);
+               REGISTER_CALLBACK (serv, "/strip/solo_safe", "ii", route_solo_safe);
                REGISTER_CALLBACK (serv, "/strip/recenable", "ii", route_recenable);
                REGISTER_CALLBACK (serv, "/strip/record_safe", "ii", route_recsafe);
                REGISTER_CALLBACK (serv, "/strip/monitor_input", "ii", route_monitor_input);
                REGISTER_CALLBACK (serv, "/strip/monitor_disk", "ii", route_monitor_disk);
                REGISTER_CALLBACK (serv, "/strip/select", "ii", strip_select);
                REGISTER_CALLBACK (serv, "/strip/gui_select", "ii", strip_gui_select);
+               REGISTER_CALLBACK (serv, "/strip/polarity", "ii", strip_phase);
                REGISTER_CALLBACK (serv, "/strip/gain", "if", route_set_gain_dB);
                REGISTER_CALLBACK (serv, "/strip/fader", "if", route_set_gain_fader);
-               REGISTER_CALLBACK (serv, "/strip/trimabs", "if", route_set_trim_abs);
                REGISTER_CALLBACK (serv, "/strip/trimdB", "if", route_set_trim_dB);
                REGISTER_CALLBACK (serv, "/strip/pan_stereo_position", "if", route_set_pan_stereo_position);
                REGISTER_CALLBACK (serv, "/strip/pan_stereo_width", "if", route_set_pan_stereo_width);
@@ -546,6 +571,7 @@ OSC::register_callbacks()
                REGISTER_CALLBACK (serv, "/strip/send/gainabs", "iif", route_set_send_gain_abs);
                REGISTER_CALLBACK (serv, "/strip/send/gain", "iif", route_set_send_gain_dB);
                REGISTER_CALLBACK (serv, "/strip/send/fader", "iif", route_set_send_fader);
+               REGISTER_CALLBACK (serv, "/strip/send/enable", "iif", route_set_send_enable);
 
                /* still not-really-standardized query interface */
                //REGISTER_CALLBACK (serv, "/ardour/*/#current_value", "", current_value);
@@ -633,7 +659,15 @@ OSC::listen_to_route (boost::shared_ptr<Stripable> strip, lo_address addr)
        OSCRouteObserver* o = new OSCRouteObserver (strip, addr, sid, s->gainmode, s->feedback);
        route_observers.push_back (o);
 
-       strip->DropReferences.connect (*this, MISSING_INVALIDATOR, boost::bind (&OSC::drop_route, this, boost::weak_ptr<Stripable> (strip)), this);
+       strip->DropReferences.connect (*this, MISSING_INVALIDATOR, boost::bind (&OSC::route_lost, this, boost::weak_ptr<Stripable> (strip)), this);
+}
+
+void
+OSC::route_lost (boost::weak_ptr<Stripable> wr)
+{
+       tick = false;
+       drop_route (wr);
+       bank_dirty = true;
 }
 
 void
@@ -882,6 +916,16 @@ OSC::catchall (const char *path, const char* types, lo_arg **argv, int argc, lo_
                        strip_gui_select (ssid, argv[0]->f == 1.0, msg);
                        ret = 0;
                }
+               else if (!strncmp (path, "/select/send_gain/", 18) && strlen (path) > 18) {
+                       int ssid = atoi (&path[18]);
+                       route_mute (ssid, argv[0]->f == 1.0, msg);
+                       ret = 0;
+               }
+               else if (!strncmp (path, "/select/send_fader/", 19) && strlen (path) > 19) {
+                       int ssid = atoi (&path[19]);
+                       route_solo (ssid, argv[0]->f == 1.0, msg);
+                       ret = 0;
+               }
        }
 
        if ((ret && _debugmode == Unhandled)) {
@@ -1228,6 +1272,36 @@ OSC::global_feedback (bitset<32> feedback, lo_address msg, uint32_t gainmode)
        }
 }
 
+void
+OSC::notify_routes_added (ARDOUR::RouteList &)
+{
+       recalcbanks();
+}
+
+void
+OSC::notify_vca_added (ARDOUR::VCAList &)
+{
+       recalcbanks();
+}
+
+void
+OSC::recalcbanks ()
+{
+       tick = false;
+       bank_dirty = true;
+}
+
+void
+OSC::_recalcbanks ()
+{
+       for (uint32_t it = 0; it < _surface.size(); ++it) {
+               OSCSurface* sur = &_surface[it];
+               // find lo_address
+               lo_address addr = lo_address_new_from_url (sur->remote_url.c_str());
+               _set_bank (sur->bank, addr);
+       }
+}
+
 /*
  * This gets called not only when bank changes but also:
  *  - bank size change
@@ -1240,6 +1314,12 @@ OSC::global_feedback (bitset<32> feedback, lo_address msg, uint32_t gainmode)
  */
 int
 OSC::set_bank (uint32_t bank_start, lo_message msg)
+{
+       return _set_bank (bank_start, lo_message_get_source (msg));
+}
+
+int
+OSC::_set_bank (uint32_t bank_start, lo_address addr)
 {
        if (!session) {
                return -1;
@@ -1258,18 +1338,18 @@ OSC::set_bank (uint32_t bank_start, lo_message msg)
                nstrips = session->nroutes() - 1;
        }
        // reset local select
-       _strip_select (0, lo_message_get_source (msg));
+       _strip_select (0, addr);
        // undo all listeners for this url
        for (int n = 0; n <= (int) nstrips; ++n) {
 
                boost::shared_ptr<Stripable> stp = session->get_remote_nth_stripable (n, PresentationInfo::Route);
 
                if (stp) {
-                       end_listen (stp, lo_message_get_source (msg));
+                       end_listen (stp, addr);
                }
        }
 
-       OSCSurface *s = get_surface (lo_message_get_source (msg));
+       OSCSurface *s = get_surface (addr);
        uint32_t b_size;
 
        if (!s->bank_size) {
@@ -1295,15 +1375,17 @@ OSC::set_bank (uint32_t bank_start, lo_message msg)
                        boost::shared_ptr<Stripable> stp = session->get_remote_nth_stripable (n - 1, PresentationInfo::Route);
 
                        if (stp) {
-                               listen_to_route(stp, lo_message_get_source (msg));
+                               listen_to_route(stp, addr);
                                if (!s->feedback[10]) {
                                        if (stp->is_selected()) {
-                                               _strip_select (n, lo_message_get_source (msg));
+                                               _strip_select (n, addr);
                                        }
                                }
                        }
                }
        }
+       bank_dirty = false;
+       tick = true;
        return 0;
 }
 
@@ -1440,17 +1522,28 @@ OSC::master_set_trim (float dB)
 }
 
 int
-OSC::master_set_pan_stereo_position (float position)
+OSC::master_set_pan_stereo_position (float position, lo_message msg)
 {
        if (!session) return -1;
 
+       float endposition = .5;
        boost::shared_ptr<Stripable> s = session->master_out();
 
        if (s) {
                if (s->pan_azimuth_control()) {
                        s->pan_azimuth_control()->set_value (position, PBD::Controllable::NoGroup);
+                       endposition = s->pan_azimuth_control()->get_value ();
                }
        }
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+
+       if (sur->feedback[4]) {
+               lo_message reply = lo_message_new ();
+               lo_message_add_float (reply, endposition);
+
+               lo_send_message (lo_message_get_source (msg), "/master/pan_stereo_position", reply);
+               lo_message_free (reply);
+       }
 
        return 0;
 }
@@ -1510,10 +1603,13 @@ OSC::route_mute (int ssid, int yn, lo_message msg)
        boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
 
        if (s) {
-               s->mute_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+               if (s->mute_control()) {
+                       s->mute_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+                       return 0;
+               }
        }
 
-       return 0;
+       return route_send_fail ("mute", ssid, 0, lo_message_get_source (msg));
 }
 
 int
@@ -1536,10 +1632,49 @@ OSC::route_solo (int ssid, int yn, lo_message msg)
        boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
 
        if (s) {
-               s->solo_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+               if (s->solo_control()) {
+                       s->solo_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+                       return 0;
+               }
        }
 
-       return 0;
+       return route_send_fail ("solo", ssid, 0, lo_message_get_source (msg));
+}
+
+int
+OSC::route_solo_iso (int ssid, int yn, lo_message msg)
+{
+       if (!session) return -1;
+       int rid = get_rid (ssid, lo_message_get_source (msg));
+
+       boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
+
+       if (s) {
+               if (s->solo_isolate_control()) {
+                       s->solo_isolate_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+                       return 0;
+               }
+       }
+
+       return route_send_fail ("solo_iso", ssid, 0, lo_message_get_source (msg));
+}
+
+int
+OSC::route_solo_safe (int ssid, int yn, lo_message msg)
+{
+       if (!session) return -1;
+       int rid = get_rid (ssid, lo_message_get_source (msg));
+
+       boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
+
+       if (s) {
+               if (s->solo_safe_control()) {
+                       s->solo_safe_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+                       return 0;
+               }
+       }
+
+       return route_send_fail ("solo_safe", ssid, 0, lo_message_get_source (msg));
 }
 
 int
@@ -1553,6 +1688,28 @@ OSC::sel_solo (uint32_t yn, lo_message msg)
        }
 }
 
+int
+OSC::sel_solo_iso (uint32_t yn, lo_message msg)
+{
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+       if (sur->surface_sel) {
+               return route_solo_iso(sur->surface_sel, yn, msg);
+       } else { 
+               return route_send_fail ("solo_iso", 0, 0, lo_message_get_source (msg));
+       }
+}
+
+int
+OSC::sel_solo_safe (uint32_t yn, lo_message msg)
+{
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+       if (sur->surface_sel) {
+               return route_solo_safe(sur->surface_sel, yn, msg);
+       } else { 
+               return route_send_fail ("solo_safe", 0, 0, lo_message_get_source (msg));
+       }
+}
+
 int
 OSC::sel_recenable (uint32_t yn, lo_message msg)
 {
@@ -1580,7 +1737,6 @@ OSC::route_recenable (int ssid, int yn, lo_message msg)
                        }
                }
        }
-       // hmm, not set for whatever reason tell surface
        return route_send_fail ("recenable", ssid, 0, lo_message_get_source (msg));
 }
 
@@ -1610,7 +1766,6 @@ OSC::route_recsafe (int ssid, int yn, lo_message msg)
                        }
                }
        }
-       // hmm, not set for whatever reason tell surface
        return route_send_fail ("record_safe", ssid, 0,lo_message_get_source (msg));
 }
 
@@ -1625,13 +1780,14 @@ OSC::route_monitor_input (int ssid, int yn, lo_message msg)
        if (s) {
                boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (s);
                if (track) {
-                       track->monitoring_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
-               } else {
-                       route_send_fail ("monitor_input", ssid, 0, lo_message_get_source (msg));
+                       if (track->monitoring_control()) {
+                               track->monitoring_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+                               return 0;
+                       }
                }
        }
 
-       return 0;
+       return route_send_fail ("monitor_input", ssid, 0, lo_message_get_source (msg));
 }
 
 int
@@ -1656,13 +1812,14 @@ OSC::route_monitor_disk (int ssid, int yn, lo_message msg)
        if (s) {
                boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (s);
                if (track) {
-                       track->monitoring_control()->set_value (yn ? 2.0 : 0.0, PBD::Controllable::NoGroup);
-               } else {
-                       route_send_fail ("monitor_disk", ssid, 0, lo_message_get_source (msg));
+                       if (track->monitoring_control()) {
+                               track->monitoring_control()->set_value (yn ? 2.0 : 0.0, PBD::Controllable::NoGroup);
+                               return 0;
+                       }
                }
        }
 
-       return 0;
+       return route_send_fail ("monitor_disk", ssid, 0, lo_message_get_source (msg));
 }
 
 int
@@ -1676,6 +1833,36 @@ OSC::sel_monitor_disk (uint32_t yn, lo_message msg)
        }
 }
 
+
+int
+OSC::strip_phase (int ssid, int yn, lo_message msg)
+{
+       if (!session) return -1;
+       int rid = get_rid (ssid, lo_message_get_source (msg));
+
+       boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
+
+       if (s) {
+               if (s->phase_control()) {
+                       s->phase_control()->set_value (yn ? 1.0 : 0.0, PBD::Controllable::NoGroup);
+                       return 0;
+               }
+       }
+
+       return route_send_fail ("polarity", ssid, 0, lo_message_get_source (msg));
+}
+
+int
+OSC::sel_phase (uint32_t yn, lo_message msg)
+{
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+       if (sur->surface_sel) {
+               return strip_phase(sur->surface_sel, yn, msg);
+       } else { 
+               return route_send_fail ("polarity", 0, 0, lo_message_get_source (msg));
+       }
+}
+
 int
 OSC::strip_select (int ssid, int yn, lo_message msg)
 {
@@ -1704,6 +1891,7 @@ OSC::_strip_select (int ssid, lo_address addr)
        if (s) {
                sur->surface_sel = ssid;
                OSCSelectObserver* sel_fb = new OSCSelectObserver (s, addr, ssid, sur->gainmode, sur->feedback);
+               s->DropReferences.connect (*this, MISSING_INVALIDATOR, boost::bind (&OSC::recalcbanks, this), this);
                sur->sel_obs = sel_fb;
        } else {
                route_send_fail ("select", ssid, 0 , addr);
@@ -1755,8 +1943,7 @@ OSC::strip_gui_select (int ssid, int yn, lo_message msg)
        int rid = get_rid (ssid, lo_message_get_source (msg));
        boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
        if (s) {
-       //SetStripableSelection ((*s)->presentation_info().order());
-               SetStripableSelection (rid); //alt above may end up being better
+               SetStripableSelection (rid); 
        } else {
                route_send_fail ("gui_select", ssid, 0, lo_message_get_source (msg));
        }
@@ -1771,7 +1958,13 @@ OSC::route_set_gain_abs (int rid, float level, lo_message msg)
        boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
 
        if (s) {
-               s->gain_control()->set_value (level, PBD::Controllable::NoGroup);
+               if (s->gain_control()) {
+                       s->gain_control()->set_value (level, PBD::Controllable::NoGroup);
+               } else {
+                       return 1;
+               }
+       } else {
+               return 1;
        }
 
        return 0;
@@ -1792,9 +1985,9 @@ OSC::route_set_gain_dB (int ssid, float dB, lo_message msg)
                ret = route_set_gain_abs (rid, dB_to_coefficient (dB), msg);
        }
        if (ret != 0) {
-               route_send_fail ("gain", ssid, -193, lo_message_get_source (msg));
+               return route_send_fail ("gain", ssid, -193, lo_message_get_source (msg));
        }
-       return ret;
+       return 0;
 }
 
 int
@@ -1823,9 +2016,9 @@ OSC::route_set_gain_fader (int ssid, float pos, lo_message msg)
                ret = route_set_gain_abs (rid, slider_position_to_gain_with_max ((pos/1023), 2.0), msg);
        }
        if (ret != 0) {
-               route_send_fail ("fader", ssid, 0, lo_message_get_source (msg));
+               return route_send_fail ("fader", ssid, 0, lo_message_get_source (msg));
        }
-       return ret;
+       return 0;
 }
 
 int
@@ -1850,17 +2043,24 @@ OSC::route_set_trim_abs (int ssid, float level, lo_message msg)
        if (s) {
                if (s->trim_control()) {
                        s->trim_control()->set_value (level, PBD::Controllable::NoGroup);
+                       return 0;
                }
 
        }
 
-       return 0;
+       return -1;
 }
 
 int
 OSC::route_set_trim_dB (int ssid, float dB, lo_message msg)
 {
-       return route_set_trim_abs(ssid, dB_to_coefficient (dB), msg);
+       int ret;
+       ret = route_set_trim_abs(ssid, dB_to_coefficient (dB), msg);
+       if (ret != 0) {
+               return route_send_fail ("trimdB", ssid, 0, lo_message_get_source (msg));
+       }
+
+return 0;
 }
 
 int
@@ -1881,7 +2081,18 @@ OSC::sel_pan_position (float val, lo_message msg)
        if (sur->surface_sel) {
                return route_set_pan_stereo_position (sur->surface_sel, val, msg);
        } else { 
-               return route_send_fail ("pan_stereo_position", 0, 0, lo_message_get_source (msg));
+               return route_send_fail ("pan_stereo_position", 0, 0.5, lo_message_get_source (msg));
+       }
+}
+
+int
+OSC::sel_pan_width (float val, lo_message msg)
+{
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+       if (sur->surface_sel) {
+               return route_set_pan_stereo_width (sur->surface_sel, val, msg);
+       } else { 
+               return route_send_fail ("pan_stereo_width", 0, 1, lo_message_get_source (msg));
        }
 }
 
@@ -1896,11 +2107,11 @@ OSC::route_set_pan_stereo_position (int ssid, float pos, lo_message msg)
        if (s) {
                if(s->pan_azimuth_control()) {
                        s->pan_azimuth_control()->set_value (pos, PBD::Controllable::NoGroup);
+                       return route_send_fail ("pan_stereo_position", ssid, s->pan_azimuth_control()->get_value (), lo_message_get_source (msg));
                }
        }
 
-       return 0;
-
+       return route_send_fail ("pan_stereo_position", ssid, 0.5, lo_message_get_source (msg));
 }
 
 int
@@ -1914,11 +2125,11 @@ OSC::route_set_pan_stereo_width (int ssid, float pos, lo_message msg)
        if (s) {
                if (s->pan_width_control()) {
                        s->pan_width_control()->set_value (pos, PBD::Controllable::NoGroup);
+                       return 0;
                }
        }
 
-       return 0;
-
+       return route_send_fail ("pan_stereo_width", ssid, 1, lo_message_get_source (msg));
 }
 
 int
@@ -1943,9 +2154,10 @@ OSC::route_set_send_gain_abs (int ssid, int sid, float val, lo_message msg)
 
        if (s->send_level_controllable (sid)) {
                s->send_level_controllable (sid)->set_value (val, PBD::Controllable::NoGroup);
+               return 0;
        }
 
-       return 0;
+       return -1;
 }
 
 int
@@ -1960,13 +2172,94 @@ OSC::route_set_send_fader (int ssid, int sid, float pos, lo_message msg)
        if (!session) {
                return -1;
        }
-       int ret;
        if ((pos > 799.5) && (pos < 800.5)) {
-               ret = route_set_send_gain_abs (ssid, sid, 1.0, msg);
+               return route_set_send_gain_abs (ssid, sid, 1.0, msg);
        } else {
-               ret = route_set_send_gain_abs (ssid, sid, slider_position_to_gain_with_max ((pos/1023), 2.0), msg);
+               return route_set_send_gain_abs (ssid, sid, slider_position_to_gain_with_max ((pos/1023), 2.0), msg);
        }
-       return ret;
+}
+
+int
+OSC::sel_sendgain (int id, float val, lo_message msg)
+{
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+       int ret;
+       if (sur->surface_sel) {
+               ret = route_set_send_gain_dB(sur->surface_sel, id, val, msg);
+       }
+       if (!ret) {
+               return ret;
+       }
+       return sel_send_fail ("send_gain", id, -193, lo_message_get_source (msg));
+}
+
+int
+OSC::sel_sendfader (int id, float val, lo_message msg)
+{
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+       int ret;
+       if (sur->surface_sel) {
+               ret = route_set_send_fader(sur->surface_sel, id, val, msg);
+       }
+       if (!ret) {
+               return ret;
+       }
+       return sel_send_fail ("send_gain", id, 0, lo_message_get_source (msg));
+
+}
+
+int
+OSC::route_set_send_enable (int ssid, int sid, float val, lo_message msg)
+{
+       if (!session) {
+               return -1;
+       }
+       int rid = get_rid (ssid, lo_message_get_source (msg));
+
+       boost::shared_ptr<Stripable> s = session->get_remote_nth_stripable (rid, PresentationInfo::Route);
+
+       if (s) {
+
+               /* revert to zero-based counting */
+
+               if (sid > 0) {
+                       --sid;
+               }
+
+               if (s->send_enable_controllable (sid)) {
+                       s->send_enable_controllable (sid)->set_value (val, PBD::Controllable::NoGroup);
+                       return 0;
+               }
+
+               if (s->send_level_controllable (sid)) {
+                       return 1;
+               }
+
+       }
+
+       return -1;
+}
+
+int
+OSC::sel_sendenable (int id, float val, lo_message msg)
+{
+       OSCSurface *sur = get_surface(lo_message_get_source (msg));
+       int ret;
+       if (sur->surface_sel) {
+               ret = route_set_send_enable(sur->surface_sel, id, val, msg);
+       }
+       switch (ret) {
+               case 0:
+                       return ret;
+               case 1:
+                       return sel_send_fail ("send_enable", id, 1, lo_message_get_source (msg));
+               default:
+                       sel_send_fail ("send_enable", id, 0, lo_message_get_source (msg));
+                       return -1;
+       }
+       return -1;
+
+
 }
 
 int
@@ -2105,6 +2398,15 @@ OSC::gui_selection_changed (StripableNotificationListPtr stripables)
 bool
 OSC::periodic (void)
 {
+       if (!tick) {
+               usleep(100); // let flurry of signals subside
+               if (bank_dirty) {
+                       _recalcbanks ();
+                       bank_dirty = false;
+                       tick = true;
+               }
+       }
+
        for (GlobalObservers::iterator x = global_observers.begin(); x != global_observers.end(); x++) {
 
                OSCGlobalObserver* go;
@@ -2121,7 +2423,7 @@ OSC::periodic (void)
                        ro->tick();
                }
        }
-       for (uint32_t it = 0; it < _surface.size(); ++it) {
+       for (uint32_t it = 0; it < _surface.size(); it++) {
                OSCSurface* sur = &_surface[it];
                OSCSelectObserver* so;
                if ((so = dynamic_cast<OSCSelectObserver*>(sur->sel_obs)) != 0) {
@@ -2165,6 +2467,29 @@ OSC::route_send_fail (string path, uint32_t ssid, float val, lo_address addr)
        return 0;
 }
 
+int
+OSC::sel_send_fail (string path, uint32_t id, float val, lo_address addr)
+{
+       OSCSurface *sur = get_surface(addr);
+
+       ostringstream os;
+       lo_message reply;
+       reply = lo_message_new ();
+       if (sur->feedback[2]) {
+               os << "/select/" << path << "/" << id;
+       } else {
+               os << "/select/" << path;
+               lo_message_add_int32 (reply, id);
+       }
+       string str_pth = os.str();
+       lo_message_add_float (reply, (float) val);
+
+       lo_send_message (addr, str_pth.c_str(), reply);
+       lo_message_free (reply);
+
+       return 0;
+}
+
 XMLNode&
 OSC::get_state ()
 {