reorganize push 2 code and logic to better handle device arrival after program startup
[ardour.git] / libs / surfaces / push2 / push2.cc
index 22b9ade39899f9c8fbaf4054438b4f7a3469991d..9f1290ec094deeb0e30392310d55c9947d7509e9 100644 (file)
@@ -30,6 +30,7 @@
 #include "timecode/time.h"
 #include "timecode/bbt_time.h"
 
+#include "ardour/amp.h"
 #include "ardour/async_midi_port.h"
 #include "ardour/audioengine.h"
 #include "ardour/debug.h"
@@ -42,6 +43,8 @@
 #include "gtkmm2ext/gui_thread.h"
 #include "gtkmm2ext/rgb_macros.h"
 
+#include "canvas/colors.h"
+
 #include "canvas.h"
 #include "gui.h"
 #include "layout.h"
 
 #include "pbd/i18n.h"
 
+#ifdef PLATFORM_WINDOWS
+#define random() rand()
+#endif
+
 using namespace ARDOUR;
 using namespace std;
 using namespace PBD;
@@ -65,63 +72,11 @@ using namespace ArdourSurface;
 #define ABLETON 0x2982
 #define PUSH2   0x1967
 
-__attribute__((constructor)) static void
-register_enums ()
-{
-       EnumWriter& enum_writer (EnumWriter::instance());
-       vector<int> i;
-       vector<string> s;
-
-       MusicalMode::Type mode;
-
-#define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
-#define REGISTER_CLASS_ENUM(t,e) i.push_back (t::e); s.push_back (#e)
-
-       REGISTER_CLASS_ENUM (MusicalMode,Dorian);
-       REGISTER_CLASS_ENUM (MusicalMode, IonianMajor);
-       REGISTER_CLASS_ENUM (MusicalMode, Minor);
-       REGISTER_CLASS_ENUM (MusicalMode, HarmonicMinor);
-       REGISTER_CLASS_ENUM (MusicalMode, MelodicMinorAscending);
-       REGISTER_CLASS_ENUM (MusicalMode, MelodicMinorDescending);
-       REGISTER_CLASS_ENUM (MusicalMode, Phrygian);
-       REGISTER_CLASS_ENUM (MusicalMode, Lydian);
-       REGISTER_CLASS_ENUM (MusicalMode, Mixolydian);
-       REGISTER_CLASS_ENUM (MusicalMode, Aeolian);
-       REGISTER_CLASS_ENUM (MusicalMode, Locrian);
-       REGISTER_CLASS_ENUM (MusicalMode, PentatonicMajor);
-       REGISTER_CLASS_ENUM (MusicalMode, PentatonicMinor);
-       REGISTER_CLASS_ENUM (MusicalMode, Chromatic);
-       REGISTER_CLASS_ENUM (MusicalMode, BluesScale);
-       REGISTER_CLASS_ENUM (MusicalMode, NeapolitanMinor);
-       REGISTER_CLASS_ENUM (MusicalMode, NeapolitanMajor);
-       REGISTER_CLASS_ENUM (MusicalMode, Oriental);
-       REGISTER_CLASS_ENUM (MusicalMode, DoubleHarmonic);
-       REGISTER_CLASS_ENUM (MusicalMode, Enigmatic);
-       REGISTER_CLASS_ENUM (MusicalMode, Hirajoshi);
-       REGISTER_CLASS_ENUM (MusicalMode, HungarianMinor);
-       REGISTER_CLASS_ENUM (MusicalMode, HungarianMajor);
-       REGISTER_CLASS_ENUM (MusicalMode, Kumoi);
-       REGISTER_CLASS_ENUM (MusicalMode, Iwato);
-       REGISTER_CLASS_ENUM (MusicalMode, Hindu);
-       REGISTER_CLASS_ENUM (MusicalMode, Spanish8Tone);
-       REGISTER_CLASS_ENUM (MusicalMode, Pelog);
-       REGISTER_CLASS_ENUM (MusicalMode, HungarianGypsy);
-       REGISTER_CLASS_ENUM (MusicalMode, Overtone);
-       REGISTER_CLASS_ENUM (MusicalMode, LeadingWholeTone);
-       REGISTER_CLASS_ENUM (MusicalMode, Arabian);
-       REGISTER_CLASS_ENUM (MusicalMode, Balinese);
-       REGISTER_CLASS_ENUM (MusicalMode, Gypsy);
-       REGISTER_CLASS_ENUM (MusicalMode, Mohammedan);
-       REGISTER_CLASS_ENUM (MusicalMode, Javanese);
-       REGISTER_CLASS_ENUM (MusicalMode, Persian);
-       REGISTER_CLASS_ENUM (MusicalMode, Algerian);
-       REGISTER (mode);
-}
-
 Push2::Push2 (ARDOUR::Session& s)
        : ControlProtocol (s, string (X_("Ableton Push 2")))
        , AbstractUI<Push2Request> (name())
        , handle (0)
+       , in_use (false)
        , _modifier_state (None)
        , splash_start (0)
        , _current_layout (0)
@@ -137,7 +92,11 @@ Push2::Push2 (ARDOUR::Session& s)
        , _pressure_mode (AfterTouch)
        , selection_color (LED::Green)
        , contrast_color (LED::Green)
+       , in_range_select (false)
 {
+       /* we're going to need this */
+
+       libusb_init (NULL);
 
        build_maps ();
        build_color_map ();
@@ -146,17 +105,21 @@ Push2::Push2 (ARDOUR::Session& s)
        /* master cannot be removed, so no need to connect to going-away signal */
        master = session->master_out ();
 
-       if (open ()) {
-               throw failed_constructor ();
-       }
-
        ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
 
-       /* catch current selection, if any */
-       {
-               StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
-               stripable_selection_change (sp);
-       }
+       /* allocate graphics layouts, even though we're not using them yet */
+
+       _canvas = new Push2Canvas (*this, 960, 160);
+       mix_layout = new MixLayout (*this, *session, "globalmix");
+       scale_layout = new ScaleLayout (*this, *session, "scale");
+       track_mix_layout = new TrackMixLayout (*this, *session, "trackmix");
+       splash_layout = new SplashLayout (*this, *session, "splash");
+
+       run_event_loop ();
+
+       /* Ports exist for the life of this instance */
+
+       ports_acquire ();
 
        /* catch arrival and departure of Push2 itself */
        ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this);
@@ -164,82 +127,103 @@ Push2::Push2 (ARDOUR::Session& s)
        /* Catch port connections and disconnections */
        ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&Push2::connection_handler, this, _1, _2, _3, _4, _5), this);
 
-       /* ports might already be there */
+       /* Push 2 ports might already be there */
        port_registration_handler ();
 }
 
 Push2::~Push2 ()
 {
-       stop ();
+       selection_connection.disconnect ();
+
+       stop_event_loop (); /* this will call stop_using_device () in Quit request handler */
+       device_release ();
+       ports_release ();
+
+       if (_current_layout) {
+               _canvas->root()->remove (_current_layout);
+               _current_layout = 0;
+       }
 
-       delete track_mix_layout;
        delete mix_layout;
+       mix_layout = 0;
        delete scale_layout;
+       scale_layout = 0;
+       delete splash_layout;
+       splash_layout = 0;
 }
 
 void
-Push2::port_registration_handler ()
+Push2::run_event_loop ()
 {
-       if (!_async_in && !_async_out) {
-               /* ports not registered yet */
-               return;
-       }
+       DEBUG_TRACE (DEBUG::Push2, "start event loop\n");
+       BaseUI::run ();
+}
 
-       if (_async_in->connected() && _async_out->connected()) {
-               /* don't waste cycles here */
-               return;
-       }
+void
+Push2::stop_event_loop ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "stop event loop\n");
+       BaseUI::quit ();
+}
 
-       string input_port_name = X_("Ableton Push 2 MIDI 1 in");
-       string output_port_name = X_("Ableton Push 2 MIDI 1 out");
-       vector<string> in;
-       vector<string> out;
+int
+Push2::begin_using_device ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "begin using device\n");
 
-       AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
-       AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
+       /* set up periodic task used to push a frame buffer to the
+        * device (25fps). The device can handle 60fps, but we don't
+        * need that frame rate.
+        */
 
-       if (!in.empty() && !out.empty()) {
-               cerr << "Push2: both ports found\n";
-               cerr << "\tconnecting to " << in.front() <<  " + " << out.front() << endl;
-               if (!_async_in->connected()) {
-                       AudioEngine::instance()->connect (_async_in->name(), in.front());
-               }
-               if (!_async_out->connected()) {
-                       AudioEngine::instance()->connect (_async_out->name(), out.front());
-               }
+       Glib::RefPtr<Glib::TimeoutSource> vblank_timeout = Glib::TimeoutSource::create (40); // milliseconds
+       vblank_connection = vblank_timeout->connect (sigc::mem_fun (*this, &Push2::vblank));
+       vblank_timeout->attach (main_loop()->get_context());
+
+       connect_session_signals ();
+
+       init_buttons (true);
+       init_touch_strip ();
+       set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+       splash ();
+
+       /* catch current selection, if any so that we can wire up the pads if appropriate */
+       {
+               StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
+               stripable_selection_change (sp);
        }
+
+       request_pressure_mode ();
+
+       in_use = true;
+
+       return 0;
 }
 
 int
-Push2::open ()
+Push2::stop_using_device ()
 {
-       int err;
+       DEBUG_TRACE (DEBUG::Push2, "stop using device\n");
 
-       if (handle) {
-               /* already open */
+       if (!in_use) {
+               DEBUG_TRACE (DEBUG::Push2, "nothing to do, device not in use\n");
                return 0;
        }
 
-       if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
-               return -1;
-       }
+       init_buttons (false);
+       strip_buttons_off ();
 
-       if ((err = libusb_claim_interface (handle, 0x00))) {
-               return -1;
-       }
+       vblank_connection.disconnect ();
+       session_connections.drop_connections ();
 
-       try {
-               _canvas = new Push2Canvas (*this, 960, 160);
-               mix_layout = new MixLayout (*this, *session);
-               scale_layout = new ScaleLayout (*this, *session);
-               track_mix_layout = new TrackMixLayout (*this, *session);
-               splash_layout = new SplashLayout (*this, *session);
-       } catch (...) {
-               error << _("Cannot construct Canvas for display") << endmsg;
-               libusb_release_interface (handle, 0x00);
-               libusb_close (handle);
-               return -1;
-       }
+       in_use = false;
+       return 0;
+}
+
+int
+Push2::ports_acquire ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "acquiring ports\n");
 
        /* setup ports */
 
@@ -247,12 +231,13 @@ Push2::open ()
        _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true);
 
        if (_async_in == 0 || _async_out == 0) {
+               DEBUG_TRACE (DEBUG::Push2, "cannot register ports\n");
                return -1;
        }
 
        /* We do not add our ports to the input/output bundles because we don't
         * want users wiring them by hand. They could use JACK tools if they
-        * really insist on that.
+        * really insist on that (and use JACK)
         */
 
        _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
@@ -281,25 +266,21 @@ Push2::open ()
 
        connect_to_parser ();
 
-       return 0;
-}
+       /* Connect input port to event loop */
 
-list<boost::shared_ptr<ARDOUR::Bundle> >
-Push2::bundles ()
-{
-       list<boost::shared_ptr<ARDOUR::Bundle> > b;
+       AsyncMIDIPort* asp;
 
-       if (_output_bundle) {
-               b.push_back (_output_bundle);
-       }
+       asp = dynamic_cast<AsyncMIDIPort*> (_input_port);
+       asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &Push2::midi_input_handler), _input_port));
+       asp->xthread().attach (main_loop()->get_context());
 
-       return b;
+       return 0;
 }
 
-int
-Push2::close ()
+void
+Push2::ports_release ()
 {
-       init_buttons (false);
+       DEBUG_TRACE (DEBUG::Push2, "releasing ports\n");
 
        /* wait for button data to be flushed */
        AsyncMIDIPort* asp;
@@ -313,29 +294,57 @@ Push2::close ()
        _async_out.reset ((ARDOUR::Port*) 0);
        _input_port = 0;
        _output_port = 0;
+}
 
-       periodic_connection.disconnect ();
-       session_connections.drop_connections ();
+int
+Push2::device_acquire ()
+{
+       int err;
 
-       if (_current_layout) {
-               _canvas->root()->remove (_current_layout);
-               _current_layout = 0;
+       DEBUG_TRACE (DEBUG::Push2, "acquiring device\n");
+
+       if (handle) {
+               DEBUG_TRACE (DEBUG::Push2, "open() called with handle already set\n");
+               /* already open */
+               return 0;
        }
 
-       delete mix_layout;
-       mix_layout = 0;
-       delete scale_layout;
-       scale_layout = 0;
-       delete splash_layout;
-       splash_layout = 0;
+       if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
+               DEBUG_TRACE (DEBUG::Push2, "failed to open USB handle\n");
+               return -1;
+       }
+
+       if ((err = libusb_claim_interface (handle, 0x00))) {
+               DEBUG_TRACE (DEBUG::Push2, "failed to claim USB device\n");
+               libusb_close (handle);
+               handle = 0;
+               return -1;
+       }
+
+       return 0;
+}
 
+void
+Push2::device_release ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "releasing device\n");
        if (handle) {
                libusb_release_interface (handle, 0x00);
                libusb_close (handle);
                handle = 0;
        }
+}
 
-       return 0;
+list<boost::shared_ptr<ARDOUR::Bundle> >
+Push2::bundles ()
+{
+       list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+       if (_output_bundle) {
+               b.push_back (_output_bundle);
+       }
+
+       return b;
 }
 
 void
@@ -362,7 +371,7 @@ Push2::init_buttons (bool startup)
        */
 
        ButtonID buttons[] = { Mute, Solo, Master, Up, Right, Left, Down, Note, Session, Mix, AddTrack, Delete, Undo,
-                              Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session, DoubleLoop,
+                              Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session,
                               Quantize, Duplicate, Browse, PageRight, PageLeft, OctaveUp, OctaveDown, Layout, Scale
        };
 
@@ -378,12 +387,6 @@ Push2::init_buttons (bool startup)
                write (b->state_msg());
        }
 
-       /* Strip buttons should all be off (black) by default. They will change
-        * color to reflect various conditions
-        */
-
-       strip_buttons_off ();
-
        if (startup) {
 
                /* all other buttons are off (black) */
@@ -415,16 +418,6 @@ Push2::init_buttons (bool startup)
 bool
 Push2::probe ()
 {
-       libusb_device_handle *h;
-       libusb_init (NULL);
-
-       if ((h = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
-               DEBUG_TRACE (DEBUG::Push2, "no Push2 device found\n");
-               return false;
-       }
-
-       libusb_close (h);
-       DEBUG_TRACE (DEBUG::Push2, "Push2 device located\n");
        return true;
 }
 
@@ -448,19 +441,10 @@ Push2::do_request (Push2Request * req)
 
        } else if (req->type == Quit) {
 
-               stop ();
+               stop_using_device ();
        }
 }
 
-int
-Push2::stop ()
-{
-       BaseUI::quit ();
-       close ();
-       return 0;
-}
-
-
 void
 Push2::splash ()
 {
@@ -482,6 +466,11 @@ Push2::vblank ()
                }
        }
 
+       if (_current_layout) {
+               _current_layout->update_meters ();
+               _current_layout->update_clocks ();
+       }
+
        _canvas->vblank();
 
        return true;
@@ -498,49 +487,20 @@ Push2::set_active (bool yn)
 
        if (yn) {
 
-               /* start event loop */
-
-               BaseUI::run ();
-
-               if (open ()) {
-                       DEBUG_TRACE (DEBUG::Push2, "device open failed\n");
-                       close ();
+               if (device_acquire ()) {
                        return -1;
                }
 
-               /* Connect input port to event loop */
-
-               AsyncMIDIPort* asp;
-
-               asp = dynamic_cast<AsyncMIDIPort*> (_input_port);
-               asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &Push2::midi_input_handler), _input_port));
-               asp->xthread().attach (main_loop()->get_context());
-
-               connect_session_signals ();
-
-               /* set up periodic task used to push a frame buffer to the
-                * device (25fps). The device can handle 60fps, but we don't
-                * need that frame rate.
-                */
-
-               Glib::RefPtr<Glib::TimeoutSource> vblank_timeout = Glib::TimeoutSource::create (40); // milliseconds
-               vblank_connection = vblank_timeout->connect (sigc::mem_fun (*this, &Push2::vblank));
-               vblank_timeout->attach (main_loop()->get_context());
-
-
-               Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (1000); // milliseconds
-               periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &Push2::periodic));
-               periodic_timeout->attach (main_loop()->get_context());
-
-               init_buttons (true);
-               init_touch_strip ();
-               set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
-               splash ();
+               if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+                       begin_using_device ();
+               } else {
+                       /* begin_using_device () will get called once we're connected */
+               }
 
        } else {
-
-               stop ();
-
+               /* Control Protocol Manager never calls us with false, but
+                * insteads destroys us.
+                */
        }
 
        ControlProtocol::set_active (yn);
@@ -581,27 +541,23 @@ Push2::midi_input_handler (IOCondition ioc, MIDI::Port* port)
 
        if (ioc & IO_IN) {
 
-               // DEBUG_TRACE (DEBUG::Push2, string_compose ("something happend on  %1\n", port->name()));
+               DEBUG_TRACE (DEBUG::Push2, string_compose ("something happend on  %1\n", port->name()));
 
                AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*>(port);
                if (asp) {
                        asp->clear ();
                }
 
-               //DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name()));
-               framepos_t now = AudioEngine::instance()->sample_time();
-               port->parse (now);
+               DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name()));
+               if (in_use) {
+                       framepos_t now = AudioEngine::instance()->sample_time();
+                       port->parse (now);
+               }
        }
 
        return true;
 }
 
-bool
-Push2::periodic ()
-{
-       return true;
-}
-
 void
 Push2::connect_to_parser ()
 {
@@ -625,18 +581,30 @@ void
 Push2::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz)
 {
        DEBUG_TRACE (DEBUG::Push2, string_compose ("Sysex, %1 bytes\n", sz));
+
+       if (sz < 8) {
+               return;
+       }
+
        MidiByteArray msg (sz, raw_bytes);
-       MidiByteArray aftertouch_mode_response (9, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01, 0x1F, 0x0, 0xF7);
-       MidiByteArray polypress_mode_response (9, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01, 0x1F, 0x1, 0xF7);
-
-       if (msg == aftertouch_mode_response) {
-               _pressure_mode = AfterTouch;
-               PressureModeChange (AfterTouch);
-               cerr << "Pressure mod eis after\n";
-       } else if (msg == polypress_mode_response) {
-               _pressure_mode = PolyPressure;
-               PressureModeChange (PolyPressure);
-               cerr << "Pressure mod eis poly\n";
+       MidiByteArray push2_sysex_header (6, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01);
+
+       if (!push2_sysex_header.compare_n (msg, 6)) {
+               return;
+       }
+
+       switch (msg[6]) {
+       case 0x1f: /* pressure mode */
+               if (msg[7] == 0x0) {
+                       _pressure_mode = AfterTouch;
+                       PressureModeChange (AfterTouch);
+                       cerr << "Pressure mode is after\n";
+               } else {
+                       _pressure_mode = PolyPressure;
+                       PressureModeChange (PolyPressure);
+                       cerr << "Pressure mode is poly\n";
+               }
+               break;
        }
 }
 
@@ -813,15 +781,9 @@ Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* e
        for (FNPadMap::iterator pi = pads_with_note.first; pi != pads_with_note.second; ++pi) {
                Pad* pad = pi->second;
 
-               if (pad->do_when_pressed == Pad::FlashOn) {
-                       pad->set_color (LED::White);
-                       pad->set_state (LED::OneShot24th);
-                       write (pad->state_msg());
-               } else if (pad->do_when_pressed == Pad::FlashOff) {
-                       pad->set_color (contrast_color);
-                       pad->set_state (LED::OneShot24th);
-                       write (pad->state_msg());
-               }
+               pad->set_color (contrast_color);
+               pad->set_state (LED::OneShot24th);
+               write (pad->state_msg());
        }
 }
 
@@ -1080,17 +1042,31 @@ Push2::set_state (const XMLNode & node, int version)
 void
 Push2::other_vpot (int n, int delta)
 {
+       boost::shared_ptr<Amp> click_gain;
        switch (n) {
        case 0:
+               /* tempo control */
                break;
        case 1:
+               /* metronome gain control */
+               click_gain = session->click_gain();
+               if (click_gain) {
+                       boost::shared_ptr<AutomationControl> ac = click_gain->gain_control();
+                       if (ac) {
+                               ac->set_value (ac->interface_to_internal (
+                                                      min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
+                                              PBD::Controllable::UseGroup);
+                       }
+               }
                break;
        case 2:
                /* master gain control */
                if (master) {
                        boost::shared_ptr<AutomationControl> ac = master->gain_control();
                        if (ac) {
-                               ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup);
+                               ac->set_value (ac->interface_to_internal (
+                                                      min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
+                                              PBD::Controllable::UseGroup);
                        }
                }
                break;
@@ -1190,10 +1166,51 @@ Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
        return matched;
 }
 
+void
+Push2::port_registration_handler ()
+{
+       if (!_async_in && !_async_out) {
+               /* ports not registered yet */
+               return;
+       }
+
+       if (_async_in->connected() && _async_out->connected()) {
+               /* don't waste cycles here */
+               return;
+       }
+
+#ifdef __APPLE__
+       /* the origin of the numeric magic identifiers is known only to Ableton
+          and may change in time. This is part of how CoreMIDI works.
+       */
+       string input_port_name = X_("system:midi_capture_1319078870");
+       string output_port_name = X_("system:midi_playback_3409210341");
+#else
+       string input_port_name = X_("Ableton Push 2 MIDI 1 in");
+       string output_port_name = X_("Ableton Push 2 MIDI 1 out");
+#endif
+       vector<string> in;
+       vector<string> out;
+
+       AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
+       AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
+
+       if (!in.empty() && !out.empty()) {
+               cerr << "Push2: both ports found\n";
+               cerr << "\tconnecting to " << in.front() <<  " + " << out.front() << endl;
+               if (!_async_in->connected()) {
+                       AudioEngine::instance()->connect (_async_in->name(), in.front());
+               }
+               if (!_async_out->connected()) {
+                       AudioEngine::instance()->connect (_async_out->name(), out.front());
+               }
+       }
+}
+
 bool
 Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
 {
-       DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler  start\n");
+       DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n");
        if (!_input_port || !_output_port) {
                return false;
        }
@@ -1214,11 +1231,14 @@ Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boo
                        connection_state &= ~OutputConnected;
                }
        } else {
-               DEBUG_TRACE (DEBUG::FaderPort, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
+               DEBUG_TRACE (DEBUG::Push2, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
                /* not our ports */
                return false;
        }
 
+       DEBUG_TRACE (DEBUG::Push2, string_compose ("our ports changed connection state: %1 -> %2 connected ? %3\n",
+                                                  name1, name2, yn));
+
        if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
 
                /* XXX this is a horrible hack. Without a short sleep here,
@@ -1227,11 +1247,19 @@ Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boo
                */
 
                g_usleep (100000);
-                DEBUG_TRACE (DEBUG::FaderPort, "device now connected for both input and output\n");
-                connected ();
+                DEBUG_TRACE (DEBUG::Push2, "device now connected for both input and output\n");
+
+                /* may not have the device open if it was just plugged
+                   in. Really need USB device detection rather than MIDI port
+                   detection for this to work well.
+                */
+
+                device_acquire ();
+                begin_using_device ();
 
        } else {
                DEBUG_TRACE (DEBUG::FaderPort, "Device disconnected (input or output or both) or not yet fully connected\n");
+               stop_using_device ();
        }
 
        ConnectionChange (); /* emit signal for our GUI */
@@ -1241,12 +1269,6 @@ Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boo
        return true; /* connection status changed */
 }
 
-void
-Push2::connected ()
-{
-       request_pressure_mode ();
-}
-
 boost::shared_ptr<Port>
 Push2::output_port()
 {
@@ -1271,6 +1293,21 @@ Push2::pad_note (int row, int col) const
        return 0;
 }
 
+void
+Push2::update_selection_color ()
+{
+       boost::shared_ptr<MidiTrack> current_midi_track = current_pad_target.lock();
+
+       if (!current_midi_track) {
+               return;
+       }
+
+       selection_color = get_color_index (current_midi_track->presentation_info().color());
+       contrast_color = get_color_index (ArdourCanvas::HSV (current_midi_track->presentation_info().color()).opposite().color());
+
+       reset_pad_colors ();
+}
+
 void
 Push2::reset_pad_colors ()
 {
@@ -1452,6 +1489,8 @@ Push2::set_percussive_mode (bool yn)
 
        int drum_note = 36;
 
+       fn_pad_map.clear ();
+
        for (int row = 0; row < 8; ++row) {
 
                for (int col = 0; col < 4; ++col) {
@@ -1517,6 +1556,14 @@ Push2::stripable_selection_change (StripableNotificationListPtr selected)
        /* disconnect from pad port, if appropriate */
 
        if (current_midi_track && pad_port) {
+
+               /* XXX this could possibly leave dangling MIDI notes.
+                *
+                * A general libardour fix is required. It isn't obvious
+                * how note resolution can be done unless disconnecting
+                * becomes "slow" (i.e. deferred for as long as it takes
+                * to resolve notes).
+                */
                current_midi_track->input()->disconnect (current_midi_track->input()->nth(0), pad_port->name(), this);
        }
 
@@ -1553,9 +1600,15 @@ Push2::get_color_index (ArdourCanvas::Color rgba)
                return i->second;
        }
 
-       int r, g, b, a;
-       UINT_TO_RGBA (rgba, &r, &g, &b, &a);
-       int w = 204; /* not sure where/when we should get this value */
+       double dr, dg, db, da;
+       int r, g, b;
+       ArdourCanvas::color_to_rgba (rgba, dr, dg, db, da);
+       int w = 126; /* not sure where/when we should get this value */
+
+
+       r = (int) floor (255.0 * dr);
+       g = (int) floor (255.0 * dg);
+       b = (int) floor (255.0 * db);
 
        /* get a free index */
 
@@ -1571,20 +1624,28 @@ Push2::get_color_index (ArdourCanvas::Color rgba)
                color_map_free_list.pop();
        }
 
-       MidiByteArray palette_msg (17, 0xf0, 0x00 , 0x21, 0x1d, 0x01, 0x01, 0x03, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x01, 0x7E, 0x00, 0xF7);
-       MidiByteArray update_pallette_msg (8, 0xf0, 0x00, 0x21, 0x1d, 0x01, 0x01, 0x05, 0xF7);
-
+       MidiByteArray palette_msg (17,
+                                  0xf0,
+                                  0x00 , 0x21, 0x1d, 0x01, 0x01, 0x03, /* reset palette header */
+                                  0x00, /* index = 7 */
+                                  0x00, 0x00, /* r = 8 & 9 */
+                                  0x00, 0x00, /* g = 10 & 11 */
+                                  0x00, 0x00, /* b = 12 & 13 */
+                                  0x00, 0x00, /* w (a?) = 14 & 15*/
+                                  0xf7);
        palette_msg[7] = index;
        palette_msg[8] = r & 0x7f;
-       palette_msg[9] = r & 0x1;
+       palette_msg[9] = (r & 0x80) >> 7;
        palette_msg[10] = g & 0x7f;
-       palette_msg[11] = g & 0x1;
+       palette_msg[11] = (g & 0x80) >> 7;
        palette_msg[12] = b & 0x7f;
-       palette_msg[13] = b & 0x1;
+       palette_msg[13] = (b & 0x80) >> 7;
        palette_msg[14] = w & 0x7f;
-       palette_msg[15] = w & 0x1;
+       palette_msg[15] = w & 0x80;
 
        write (palette_msg);
+
+       MidiByteArray update_pallette_msg (8, 0xf0, 0x00, 0x21, 0x1d, 0x01, 0x01, 0x05, 0xF7);
        write (update_pallette_msg);
 
        color_map[rgba] = index;
@@ -1649,26 +1710,26 @@ Push2::get_color (ColorName name)
 void
 Push2::set_current_layout (Push2Layout* layout)
 {
-       if (_current_layout) {
-               _current_layout->hide ();
-               _canvas->root()->remove (_current_layout);
-               _previous_layout = _current_layout;
-       }
+       if (layout && layout == _current_layout) {
+               _current_layout->show ();
+       } else {
 
-       /* turn off all strip buttons - let new layout set them if it
-        * wants/needs to
-        */
+               if (_current_layout) {
+                       _current_layout->hide ();
+                       _canvas->root()->remove (_current_layout);
+                       _previous_layout = _current_layout;
+               }
 
-       strip_buttons_off ();
+               _current_layout = layout;
 
-       _current_layout = layout;
+               if (_current_layout) {
+                       _canvas->root()->add (_current_layout);
+                       _current_layout->show ();
+               }
 
-       if (_current_layout) {
-               _canvas->root()->add (_current_layout);
-               _current_layout->show ();
-       }
 
-       _canvas->request_redraw ();
+               _canvas->request_redraw ();
+       }
 }
 
 void