push2:small logic fix for destructor, mostly to ensure that the track_mix layout...
[ardour.git] / libs / surfaces / push2 / push2.cc
index b67e7e7b00c1113667231d19333f9f54d26bb218..484dbcccdb6df44b79708807d37e26e3760a3e8a 100644 (file)
@@ -1,42 +1,65 @@
 /*
-       Copyright (C) 2016 Paul Davis
+  Copyright (C) 2016 Paul Davis
 
-       This program is free software; you can redistribute it and/or modify
-       it under the terms of the GNU General Public License as published by
-       the Free Software Foundation; either version 2 of the License, or
-       (at your option) any later version.
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
 
-       This program is distributed in the hope that it will be useful,
-       but WITHOUT ANY WARRANTY; without even the implied warranty of
-       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-       GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
 
-       You should have received a copy of the GNU General Public License
-       along with this program; if not, write to the Free Software
-       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
-#include <cairomm/context.h>
-#include <cairomm/surface.h>
-#include <pangomm/layout.h>
+#include <stdlib.h>
 
 #include "pbd/compose.h"
 #include "pbd/convert.h"
 #include "pbd/debug.h"
 #include "pbd/failed_constructor.h"
+#include "pbd/file_utils.h"
+#include "pbd/search_path.h"
+#include "pbd/enumwriter.h"
 
 #include "midi++/parser.h"
 #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"
 #include "ardour/midiport_manager.h"
+#include "ardour/midi_track.h"
+#include "ardour/midi_port.h"
 #include "ardour/session.h"
 #include "ardour/tempo.h"
 
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/rgb_macros.h"
+
+#include "canvas/colors.h"
+
+#include "canvas.h"
+#include "gui.h"
+#include "layout.h"
+#include "menu.h"
+#include "mix.h"
 #include "push2.h"
+#include "scale.h"
+#include "splash.h"
+#include "track_mix.h"
+
+#include "pbd/i18n.h"
+
+#ifdef PLATFORM_WINDOWS
+#define random() rand()
+#endif
 
 using namespace ARDOUR;
 using namespace std;
@@ -44,14 +67,8 @@ using namespace PBD;
 using namespace Glib;
 using namespace ArdourSurface;
 
-#include "i18n.h"
-
 #include "pbd/abstract_ui.cc" // instantiate template
 
-const int Push2::cols = 960;
-const int Push2::rows = 160;
-const int Push2::pixels_per_row = 1024;
-
 #define ABLETON 0x2982
 #define PUSH2   0x1967
 
@@ -59,97 +76,221 @@ Push2::Push2 (ARDOUR::Session& s)
        : ControlProtocol (s, string (X_("Ableton Push 2")))
        , AbstractUI<Push2Request> (name())
        , handle (0)
-       , device_buffer (0)
-       , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, cols, rows))
-       , modifier_state (None)
-       , bank_start (0)
+       , in_use (false)
+       , _modifier_state (None)
+       , splash_start (0)
+       , _current_layout (0)
+       , _previous_layout (0)
+       , connection_state (ConnectionState (0))
+       , gui (0)
+       , _mode (MusicalMode::IonianMajor)
+       , _scale_root (0)
+       , _root_octave (3)
+       , _in_key (true)
+       , octave_shift (0)
+       , percussion (false)
+       , _pressure_mode (AfterTouch)
+       , selection_color (LED::Green)
+       , contrast_color (LED::Green)
+       , in_range_select (false)
 {
-       context = Cairo::Context::create (frame_buffer);
-       tc_clock_layout = Pango::Layout::create (context);
-       bbt_clock_layout = Pango::Layout::create (context);
+       /* we're going to need this */
 
-       Pango::FontDescription fd ("Sans Bold 24");
-       tc_clock_layout->set_font_description (fd);
-       bbt_clock_layout->set_font_description (fd);
+       libusb_init (NULL);
 
-       Pango::FontDescription fd2 ("Sans 10");
-       for (int n = 0; n < 8; ++n) {
-               upper_layout[n] = Pango::Layout::create (context);
-               upper_layout[n]->set_font_description (fd2);
-               upper_layout[n]->set_text ("solo");
-               lower_layout[n] = Pango::Layout::create (context);
-               lower_layout[n]->set_font_description (fd2);
-               lower_layout[n]->set_text ("mute");
-       }
+       build_maps ();
+       build_color_map ();
+       fill_color_table ();
 
-       Pango::FontDescription fd3 ("Sans Bold 10");
-       for (int n = 0; n < 8; ++n) {
-               mid_layout[n] = Pango::Layout::create (context);
-               mid_layout[n]->set_font_description (fd3);
-       }
+       /* master cannot be removed, so no need to connect to going-away signal */
+       master = session->master_out ();
 
-       build_maps ();
+       ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
 
-       if (open ()) {
-               throw failed_constructor ();
-       }
+       /* 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);
+
+       /* 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);
+
+       /* Push 2 ports might already be there */
+       port_registration_handler ();
 }
 
 Push2::~Push2 ()
 {
-       stop ();
+       DEBUG_TRACE (DEBUG::Push2, "push2 control surface object being destroyed\n");
+
+       /* do this before stopping the event loop, so that we don't get any notifications */
+       selection_connection.disconnect ();
+       port_reg_connection.disconnect ();
+       port_connection.disconnect ();
+
+       stop_using_device ();
+       device_release ();
+       ports_release ();
+
+       if (_current_layout) {
+               _canvas->root()->remove (_current_layout);
+               _current_layout = 0;
+       }
+
+       delete mix_layout;
+       mix_layout = 0;
+       delete scale_layout;
+       scale_layout = 0;
+       delete splash_layout;
+       splash_layout = 0;
+       delete track_mix_layout;
+       track_mix_layout = 0;
+
+       stop_event_loop ();
+}
+
+
+void
+Push2::run_event_loop ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "start event loop\n");
+       BaseUI::run ();
+}
+
+void
+Push2::stop_event_loop ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "stop event loop\n");
+       BaseUI::quit ();
 }
 
 int
-Push2::open ()
+Push2::begin_using_device ()
 {
-       int err;
+       DEBUG_TRACE (DEBUG::Push2, "begin using device\n");
 
-       if (handle) {
-               /* already open */
-               return 0;
-       }
+       /* 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 ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
-               return -1;
+       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);
        }
 
-       if ((err = libusb_claim_interface (handle, 0x00))) {
-               return -1;
+       request_pressure_mode ();
+
+       in_use = true;
+
+       return 0;
+}
+
+int
+Push2::stop_using_device ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "stop using device\n");
+
+       if (!in_use) {
+               DEBUG_TRACE (DEBUG::Push2, "nothing to do, device not in use\n");
+               return 0;
        }
 
-       device_frame_buffer = new uint16_t[rows*pixels_per_row];
+       init_buttons (false);
+       strip_buttons_off ();
+
+       vblank_connection.disconnect ();
+       session_connections.drop_connections ();
 
-       memset (device_frame_buffer, 0, sizeof (uint16_t) * rows * pixels_per_row);
+       in_use = false;
+       return 0;
+}
 
-       frame_header[0] = 0xef;
-       frame_header[1] = 0xcd;
-       frame_header[2] = 0xab;
-       frame_header[3] = 0x89;
-       memset (&frame_header[4], 0, 12);
+int
+Push2::ports_acquire ()
+{
+       DEBUG_TRACE (DEBUG::Push2, "acquiring ports\n");
 
        /* setup ports */
 
-       _async_in  = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("push2 in"), true);
-       _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("push2 out"), true);
+       _async_in  = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Push 2 in"), true);
+       _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 (and use JACK)
+        */
+
        _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
        _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
 
+       /* Create a shadow port where, depending on the state of the surface,
+        * we will make pad note on/off events appear. The surface code will
+        * automatically this port to the first selected MIDI track.
+        */
+
+       boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->add_shadow_port (string_compose (_("%1 Pads"), X_("Push 2")), boost::bind (&Push2::pad_filter, this, _1, _2));
+       boost::shared_ptr<MidiPort> shadow_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->shadow_port();
+
+       if (shadow_port) {
+
+               _output_bundle.reset (new ARDOUR::Bundle (_("Push 2 Pads"), false));
+
+               _output_bundle->add_channel (
+                       shadow_port->name(),
+                       ARDOUR::DataType::MIDI,
+                       session->engine().make_port_name_non_relative (shadow_port->name())
+                       );
+       }
+
+       session->BundleAddedOrRemoved ();
+
        connect_to_parser ();
 
+       /* 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());
+
        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;
@@ -163,28 +304,75 @@ Push2::close ()
        _async_out.reset ((ARDOUR::Port*) 0);
        _input_port = 0;
        _output_port = 0;
+}
 
-       vblank_connection.disconnect ();
-       periodic_connection.disconnect ();
-       session_connections.drop_connections ();
-       stripable_connections.drop_connections ();
+int
+Push2::device_acquire ()
+{
+       int err;
+
+       DEBUG_TRACE (DEBUG::Push2, "acquiring device\n");
+
+       if (handle) {
+               DEBUG_TRACE (DEBUG::Push2, "open() called with handle already set\n");
+               /* already open */
+               return 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;
        }
+}
+
+list<boost::shared_ptr<ARDOUR::Bundle> >
+Push2::bundles ()
+{
+       list<boost::shared_ptr<ARDOUR::Bundle> > b;
 
-       for (int n = 0; n < 8; ++n) {
-               stripable[n].reset ();
+       if (_output_bundle) {
+               b.push_back (_output_bundle);
        }
 
-       delete [] device_frame_buffer;
-       device_frame_buffer = 0;
+       return b;
+}
 
-       return 0;
+void
+Push2::strip_buttons_off ()
+{
+       ButtonID strip_buttons[] = { Upper1, Upper2, Upper3, Upper4, Upper5, Upper6, Upper7, Upper8,
+                                    Lower1, Lower2, Lower3, Lower4, Lower5, Lower6, Lower7, Lower8, };
+
+       for (size_t n = 0; n < sizeof (strip_buttons) / sizeof (strip_buttons[0]); ++n) {
+               Button* b = id_button_map[strip_buttons[n]];
+
+               b->set_color (LED::Black);
+               b->set_state (LED::OneShot24th);
+               write (b->state_msg());
+       }
 }
 
+
 void
 Push2::init_buttons (bool startup)
 {
@@ -193,8 +381,8 @@ 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,
-                              Quantize, Duplicate, Browse,
+                              Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session,
+                              Quantize, Duplicate, Browse, PageRight, PageLeft, OctaveUp, OctaveDown, Layout, Scale
        };
 
        for (size_t n = 0; n < sizeof (buttons) / sizeof (buttons[0]); ++n) {
@@ -209,28 +397,13 @@ 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
-        */
-
-       ButtonID strip_buttons[] = { Upper1, Upper2, Upper3, Upper4, Upper5, Upper6, Upper7, Upper8,
-                                    Lower1, Lower2, Lower3, Lower4, Lower5, Lower6, Lower7, Lower8, };
-
-       for (size_t n = 0; n < sizeof (strip_buttons) / sizeof (strip_buttons[0]); ++n) {
-               Button* b = id_button_map[strip_buttons[n]];
-
-               b->set_color (LED::Black);
-               b->set_state (LED::OneShot24th);
-               write (b->state_msg());
-       }
-
        if (startup) {
 
                /* all other buttons are off (black) */
 
                ButtonID off_buttons[] = { TapTempo, Setup, User, Stop, Convert, New, FixedLength,
                                           Fwd32ndT, Fwd32nd, Fwd16thT, Fwd16th, Fwd8thT, Fwd8th, Fwd4trT, Fwd4tr,
-                                          Accent, Scale, Layout, Note, Session, OctaveUp, PageRight, OctaveDown, PageLeft, };
+                                          Accent, Note, Session,  };
 
                for (size_t n = 0; n < sizeof (off_buttons) / sizeof (off_buttons[0]); ++n) {
                        Button* b = id_button_map[off_buttons[n]];
@@ -241,21 +414,20 @@ Push2::init_buttons (bool startup)
                }
        }
 
+       if (!startup) {
+               for (NNPadMap::iterator pi = nn_pad_map.begin(); pi != nn_pad_map.end(); ++pi) {
+                       Pad* pad = pi->second;
+
+                       pad->set_color (LED::Black);
+                       pad->set_state (LED::OneShot24th);
+                       write (pad->state_msg());
+               }
+       }
 }
 
 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;
 }
 
@@ -273,207 +445,43 @@ Push2::request_factory (uint32_t num_requests)
 void
 Push2::do_request (Push2Request * req)
 {
-       DEBUG_TRACE (DEBUG::Push2, string_compose ("doing request type %1\n", req->type));
        if (req->type == CallSlot) {
 
                call_slot (MISSING_INVALIDATOR, req->the_slot);
 
        } else if (req->type == Quit) {
 
-               stop ();
+               stop_using_device ();
        }
 }
 
-int
-Push2::stop ()
-{
-       BaseUI::quit ();
-       close ();
-       return 0;
-}
-
-/** render host-side frame buffer (a Cairo ImageSurface) to the current
- * device-side frame buffer. The device frame buffer will be pushed to the
- * device on the next call to vblank()
- */
-
-int
-Push2::blit_to_device_frame_buffer ()
+void
+Push2::splash ()
 {
-       /* ensure that all drawing has been done before we fetch pixel data */
-
-       frame_buffer->flush ();
-
-       const int stride = 3840; /* bytes per row for Cairo::FORMAT_ARGB32 */
-       const uint8_t* data = frame_buffer->get_data ();
-
-       /* fill frame buffer (320kB) */
-
-       uint16_t* fb = (uint16_t*) device_frame_buffer;
-
-       for (int row = 0; row < rows; ++row) {
-
-               const uint8_t* dp = data + row * stride;
-
-               for (int col = 0; col < cols; ++col) {
-
-                       /* fetch r, g, b (range 0..255). Ignore alpha */
-
-                       const int r = (*((const uint32_t*)dp) >> 16) & 0xff;
-                       const int g = (*((const uint32_t*)dp) >> 8) & 0xff;
-                       const int b = *((const uint32_t*)dp) & 0xff;
-
-                       /* convert to 5 bits, 6 bits, 5 bits, respectively */
-                       /* generate 16 bit BGB565 value */
-
-                       *fb++ = (r >> 3) | ((g & 0xfc) << 3) | ((b & 0xf8) << 8);
-
-                       /* the push2 docs state that we should xor the pixel
-                        * data. Doing so doesn't work correctly, and not doing
-                        * so seems to work fine (colors roughly match intended
-                        * values).
-                        */
-
-                       dp += 4;
-               }
-
-               /* skip 128 bytes to next line. This is filler, used to avoid line borders occuring in the middle of 512
-                  byte USB buffers
-               */
-
-               fb += 64; /* 128 bytes = 64 int16_t */
-       }
-
-       return 0;
+       set_current_layout (splash_layout);
+       splash_start = get_microseconds ();
 }
 
 bool
-Push2::redraw ()
+Push2::vblank ()
 {
-       string tc_clock_text;
-       string bbt_clock_text;
-
-       if (session) {
-               framepos_t audible = session->audible_frame();
-               Timecode::Time TC;
-               bool negative = false;
-
-               if (audible < 0) {
-                       audible = -audible;
-                       negative = true;
-               }
-
-               session->timecode_time (audible, TC);
-
-               TC.negative = TC.negative || negative;
-
-               tc_clock_text = Timecode::timecode_format_time(TC);
-
-               Timecode::BBT_Time bbt = session->tempo_map().bbt_at_frame (audible);
-               char buf[16];
-
-#define BBT_BAR_CHAR "|"
-
-               if (negative) {
-                       snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
-                                 bbt.bars, bbt.beats, bbt.ticks);
-               } else {
-                       snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
-                                 bbt.bars, bbt.beats, bbt.ticks);
-               }
-
-               bbt_clock_text = buf;
-       }
-
-       bool dirty = false;
-
-       if (tc_clock_text != tc_clock_layout->get_text()) {
-               dirty = true;
-               tc_clock_layout->set_text (tc_clock_text);
-       }
-
-       if (bbt_clock_text != tc_clock_layout->get_text()) {
-               dirty = true;
-               bbt_clock_layout->set_text (bbt_clock_text);
-       }
-
-       string mid_text;
-
-       for (int n = 0; n < 8; ++n) {
-               if (stripable[n]) {
-                       mid_text = short_version (stripable[n]->name(), 10);
-                       if (mid_text != mid_layout[n]->get_text()) {
-                               mid_layout[n]->set_text (mid_text);
-                               dirty = true;
-                       }
-               }
-       }
-
-       if (!dirty) {
-               return false;
-       }
-
-       context->set_source_rgb (0.764, 0.882, 0.882);
-       context->rectangle (0, 0, 960, 160);
-       context->fill ();
-       context->set_source_rgb (0.23, 0.0, 0.349);
-       context->move_to (650, 25);
-       tc_clock_layout->update_from_cairo_context (context);
-       tc_clock_layout->show_in_cairo_context (context);
-       context->move_to (650, 60);
-       bbt_clock_layout->update_from_cairo_context (context);
-       bbt_clock_layout->show_in_cairo_context (context);
-
-       for (int n = 0; n < 8; ++n) {
-               context->move_to (10 + (n*120), 2);
-               upper_layout[n]->update_from_cairo_context (context);
-               upper_layout[n]->show_in_cairo_context (context);
-       }
+       if (splash_start) {
 
-       for (int n = 0; n < 8; ++n) {
-               context->move_to (10 + (n*120), 140);
-               lower_layout[n]->update_from_cairo_context (context);
-               lower_layout[n]->show_in_cairo_context (context);
-       }
+               /* display splash for 2 seconds */
 
-       for (int n = 0; n < 8; ++n) {
-               if (stripable[n] && stripable[n]->presentation_info().selected()) {
-                       context->rectangle (10 + (n*120) - 5, 115, 120, 22);
-                       context->set_source_rgb (1.0, 0.737, 0.172);
-                       context->fill();
+               if (get_microseconds() - splash_start > 2000000) {
+                       splash_start = 0;
+                       DEBUG_TRACE (DEBUG::Push2, "splash interval ended, switch to mix layout\n");
+                       set_current_layout (mix_layout);
                }
-               context->set_source_rgb (0.0, 0.0, 0.0);
-               context->move_to (10 + (n*120), 120);
-               mid_layout[n]->update_from_cairo_context (context);
-               mid_layout[n]->show_in_cairo_context (context);
-       }
-
-       /* render clock */
-       /* render foo */
-       /* render bar */
-
-       return true;
-}
-
-bool
-Push2::vblank ()
-{
-       int transferred = 0;
-       const int timeout_msecs = 1000;
-       int err;
-
-       if ((err = libusb_bulk_transfer (handle, 0x01, frame_header, sizeof (frame_header), &transferred, timeout_msecs))) {
-               return false;
        }
 
-       if (redraw()) {
-               /* things changed */
-               blit_to_device_frame_buffer ();
+       if (_current_layout) {
+               _current_layout->update_meters ();
+               _current_layout->update_clocks ();
        }
 
-       if ((err = libusb_bulk_transfer (handle, 0x01, (uint8_t*) device_frame_buffer , 2 * rows * pixels_per_row, &transferred, timeout_msecs))) {
-               return false;
-       }
+       _canvas->vblank();
 
        return true;
 }
@@ -489,48 +497,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 ();
-               switch_bank (0);
+               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);
@@ -571,27 +551,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 ()
 {
@@ -615,17 +591,42 @@ 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));
-}
 
-void
-Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
-{
-       DEBUG_TRACE (DEBUG::Push2, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
+       if (sz < 8) {
+               return;
+       }
 
-       CCButtonMap::iterator b = cc_button_map.find (ev->controller_number);
+       MidiByteArray msg (sz, raw_bytes);
+       MidiByteArray push2_sysex_header (6, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01);
 
-       if (ev->value) {
-               /* any press cancels any pending long press timeouts */
+       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;
+       }
+}
+
+void
+Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
+{
+       DEBUG_TRACE (DEBUG::Push2, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
+
+       CCButtonMap::iterator b = cc_button_map.find (ev->controller_number);
+
+       if (ev->value) {
+               /* any press cancels any pending long press timeouts */
                for (set<ButtonID>::iterator x = buttons_down.begin(); x != buttons_down.end(); ++x) {
                        Button* bb = id_button_map[*x];
                        bb->timeout_connection.disconnect ();
@@ -670,33 +671,33 @@ Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
 
                switch (ev->controller_number) {
                case 71:
-                       strip_vpot (0, delta);
+                       _current_layout->strip_vpot (0, delta);
                        break;
                case 72:
-                       strip_vpot (1, delta);
+                       _current_layout->strip_vpot (1, delta);
                        break;
                case 73:
-                       strip_vpot (2, delta);
+                       _current_layout->strip_vpot (2, delta);
                        break;
                case 74:
-                       strip_vpot (3, delta);
+                       _current_layout->strip_vpot (3, delta);
                        break;
                case 75:
-                       strip_vpot (4, delta);
+                       _current_layout->strip_vpot (4, delta);
                        break;
                case 76:
-                       strip_vpot (5, delta);
+                       _current_layout->strip_vpot (5, delta);
                        break;
                case 77:
-                       strip_vpot (6, delta);
+                       _current_layout->strip_vpot (6, delta);
                        break;
                case 78:
-                       strip_vpot (7, delta);
+                       _current_layout->strip_vpot (7, delta);
                        break;
 
                        /* left side pair */
                case 14:
-                       strip_vpot (8, delta);
+                       other_vpot (8, delta);
                        break;
                case 15:
                        other_vpot (1, delta);
@@ -711,34 +712,39 @@ Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
 }
 
 void
-Push2::handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
+Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev)
 {
-       DEBUG_TRACE (DEBUG::Push2, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
+       // DEBUG_TRACE (DEBUG::Push2, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
+
+       if (ev->velocity == 0) {
+               handle_midi_note_off_message (parser, ev);
+               return;
+       }
 
        switch (ev->note_number) {
        case 0:
-               strip_vpot_touch (0, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (0, ev->velocity > 64);
                break;
        case 1:
-               strip_vpot_touch (1, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (1, ev->velocity > 64);
                break;
        case 2:
-               strip_vpot_touch (2, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (2, ev->velocity > 64);
                break;
        case 3:
-               strip_vpot_touch (3, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (3, ev->velocity > 64);
                break;
        case 4:
-               strip_vpot_touch (4, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (4, ev->velocity > 64);
                break;
        case 5:
-               strip_vpot_touch (5, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (5, ev->velocity > 64);
                break;
        case 6:
-               strip_vpot_touch (6, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (6, ev->velocity > 64);
                break;
        case 7:
-               strip_vpot_touch (7, ev->velocity > 64);
+               _current_layout->strip_vpot_touch (7, ev->velocity > 64);
                break;
 
                /* left side */
@@ -761,27 +767,83 @@ Push2::handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
                }
                break;
        }
+
+       if (ev->note_number < 11) {
+               return;
+       }
+
+       /* Pad illuminations */
+
+       NNPadMap::const_iterator pm = nn_pad_map.find (ev->note_number);
+
+       if (pm == nn_pad_map.end()) {
+               return;
+       }
+
+       const Pad * const pad_pressed = pm->second;
+
+       pair<FNPadMap::iterator,FNPadMap::iterator> pads_with_note = fn_pad_map.equal_range (pad_pressed->filtered);
+
+       if (pads_with_note.first == fn_pad_map.end()) {
+               return;
+       }
+
+       for (FNPadMap::iterator pi = pads_with_note.first; pi != pads_with_note.second; ++pi) {
+               Pad* pad = pi->second;
+
+               pad->set_color (contrast_color);
+               pad->set_state (LED::OneShot24th);
+               write (pad->state_msg());
+       }
 }
 
 void
 Push2::handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
 {
-       DEBUG_TRACE (DEBUG::Push2, string_compose ("Note Off %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
-}
+       // DEBUG_TRACE (DEBUG::Push2, string_compose ("Note Off %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
 
-void
-Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb)
-{
-       if (!session) {
+       if (ev->note_number < 11) {
+               /* theoretically related to encoder touch start/end, but
+                * actually they send note on with two different velocity
+                * values (127 & 64).
+                */
                return;
        }
 
-       float speed;
+       /* Pad illuminations */
+
+       NNPadMap::const_iterator pm = nn_pad_map.find (ev->note_number);
+
+       if (pm == nn_pad_map.end()) {
+               return;
+       }
+
+       const Pad * const pad_pressed = pm->second;
+
+       pair<FNPadMap::iterator,FNPadMap::iterator> pads_with_note = fn_pad_map.equal_range (pad_pressed->filtered);
+
+       if (pads_with_note.first == fn_pad_map.end()) {
+               return;
+       }
+
+       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::Black);
+                       pad->set_state (LED::OneShot24th);
+                       write (pad->state_msg());
+               } else if (pad->do_when_pressed == Pad::FlashOff) {
+                       pad->set_color (pad->perma_color);
+                       pad->set_state (LED::OneShot24th);
+                       write (pad->state_msg());
+               }
+       }
+}
 
-       /* range of +1 .. -1 */
-       speed = ((int32_t) pb - 8192) / 8192.0;
-       /* convert to range of +3 .. -3 */
-       session->request_transport_speed (speed * 3.0);
+void
+Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb)
+{
 }
 
 void
@@ -931,6 +993,11 @@ Push2::get_state()
        child->add_child_nocopy (_async_out->get_state());
        node.add_child_nocopy (*child);
 
+       node.add_property (X_("root"), to_string (_scale_root, std::dec));
+       node.add_property (X_("root_octave"), to_string (_root_octave, std::dec));
+       node.add_property (X_("in_key"), _in_key ? X_("yes") : X_("no"));
+       node.add_property (X_("mode"), enum_2_string (_mode));
+
        return node;
 }
 
@@ -961,291 +1028,751 @@ Push2::set_state (const XMLNode & node, int version)
                }
        }
 
+       XMLProperty const* prop;
+
+       if ((prop = node.property (X_("root"))) != 0) {
+               _scale_root = atoi (prop->value());
+       }
+
+       if ((prop = node.property (X_("root_octave"))) != 0) {
+               _root_octave = atoi (prop->value());
+       }
+
+       if ((prop = node.property (X_("in_key"))) != 0) {
+               _in_key = string_is_affirmative (prop->value());
+       }
+
+       if ((prop = node.property (X_("mode"))) != 0) {
+               _mode = (MusicalMode::Type) string_2_enum (prop->value(), _mode);
+       }
+
        return retval;
 }
 
 void
-Push2::switch_bank (uint32_t base)
+Push2::other_vpot (int n, int delta)
 {
-       if (!session) {
-               return;
+       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->interface_to_internal (
+                                                      min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
+                                              PBD::Controllable::UseGroup);
+                       }
+               }
+               break;
        }
+}
 
-       stripable_connections.drop_connections ();
-
-       /* try to get the first stripable for the requested bank */
+void
+Push2::other_vpot_touch (int n, bool touching)
+{
+       switch (n) {
+       case 0:
+               break;
+       case 1:
+               break;
+       case 2:
+               if (master) {
+                       boost::shared_ptr<AutomationControl> ac = master->gain_control();
+                       if (ac) {
+                               if (touching) {
+                                       ac->start_touch (session->audible_frame());
+                               } else {
+                                       ac->stop_touch (true, session->audible_frame());
+                               }
+                       }
+               }
+       }
+}
 
-       stripable[0] = session->get_remote_nth_stripable (base, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
+void
+Push2::start_shift ()
+{
+       cerr << "start shift\n";
+       _modifier_state = ModifierState (_modifier_state | ModShift);
+       Button* b = id_button_map[Shift];
+       b->set_color (LED::White);
+       b->set_state (LED::Blinking16th);
+       write (b->state_msg());
+}
 
-       if (!stripable[0]) {
-               return;
+void
+Push2::end_shift ()
+{
+       if (_modifier_state & ModShift) {
+               cerr << "end shift\n";
+               _modifier_state = ModifierState (_modifier_state & ~(ModShift));
+               Button* b = id_button_map[Shift];
+               b->timeout_connection.disconnect ();
+               b->set_color (LED::White);
+               b->set_state (LED::OneShot24th);
+               write (b->state_msg());
        }
+}
 
-       /* at least one stripable in this bank */
-       bank_start = base;
+bool
+Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
+{
+       /* This filter is called asynchronously from a realtime process
+          context. It must use atomics to check state, and must not block.
+       */
 
-       stripable[1] = session->get_remote_nth_stripable (base+1, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
-       stripable[2] = session->get_remote_nth_stripable (base+2, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
-       stripable[3] = session->get_remote_nth_stripable (base+3, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
-       stripable[4] = session->get_remote_nth_stripable (base+4, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
-       stripable[5] = session->get_remote_nth_stripable (base+5, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
-       stripable[6] = session->get_remote_nth_stripable (base+6, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
-       stripable[7] = session->get_remote_nth_stripable (base+7, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
+       bool matched = false;
+
+       for (MidiBuffer::iterator ev = in.begin(); ev != in.end(); ++ev) {
+               if ((*ev).is_note_on() || (*ev).is_note_off()) {
+
+                       /* encoder touch start/touch end use note
+                        * 0-10. touchstrip uses note 12
+                        */
 
+                       if ((*ev).note() > 10 && (*ev).note() != 12) {
+
+                               const int n = (*ev).note ();
+                               NNPadMap::const_iterator nni = nn_pad_map.find (n);
+
+                               if (nni != nn_pad_map.end()) {
+                                       Pad const * pad = nni->second;
+                                       /* shift for output to the shadow port */
+                                       if (pad->filtered >= 0) {
+                                               (*ev).set_note (pad->filtered + (octave_shift*12));
+                                               out.push_back (*ev);
+                                               /* shift back so that the pads light correctly  */
+                                               (*ev).set_note (n);
+                                       } else {
+                                               /* no mapping, don't send event */
+                                       }
+                               } else {
+                                       out.push_back (*ev);
+                               }
 
-       for (int n = 0; n < 8; ++n) {
-               if (!stripable[n]) {
-                       continue;
+                               matched = true;
+                       }
+               } else if ((*ev).is_pitch_bender() || (*ev).is_poly_pressure() || (*ev).is_channel_pressure()) {
+                       out.push_back (*ev);
                }
+       }
+
+       return matched;
+}
+
+void
+Push2::port_registration_handler ()
+{
+       if (!_async_in && !_async_out) {
+               /* ports not registered yet */
+               return;
+       }
 
-               /* stripable goes away? refill the bank, starting at the same point */
+       if (_async_in->connected() && _async_out->connected()) {
+               /* don't waste cycles here */
+               return;
+       }
 
-               stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::switch_bank, this, bank_start), this);
-               boost::shared_ptr<AutomationControl> sc = stripable[n]->solo_control();
-               if (sc) {
-                       sc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::solo_change, this, n), this);
+#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());
+               }
+       }
+}
 
-               boost::shared_ptr<AutomationControl> mc = stripable[n]->mute_control();
-               if (mc) {
-                       mc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::mute_change, this, n), this);
+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");
+       if (!_input_port || !_output_port) {
+               return false;
+       }
+
+       string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_in)->name());
+       string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_out)->name());
+
+       if (ni == name1 || ni == name2) {
+               if (yn) {
+                       connection_state |= InputConnected;
+               } else {
+                       connection_state &= ~InputConnected;
                }
+       } else if (no == name1 || no == name2) {
+               if (yn) {
+                       connection_state |= OutputConnected;
+               } else {
+                       connection_state &= ~OutputConnected;
+               }
+       } else {
+               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,
+                  something prevents the device wakeup messages from being
+                  sent and/or the responses from being received.
+               */
+
+               g_usleep (100000);
+                DEBUG_TRACE (DEBUG::Push2, "device now connected for both input and output\n");
 
-               stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_property_change, this, _1, n), this);
+                /* 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.
+                */
 
-               solo_change (n);
-               mute_change (n);
+                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 ();
        }
 
-       /* master cannot be removed, so no need to connect to going-away signal */
-       master = session->master_out ();
+       ConnectionChange (); /* emit signal for our GUI */
+
+       DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler  end\n");
+
+       return true; /* connection status changed */
 }
 
-void
-Push2::stripable_property_change (PropertyChange const& what_changed, int which)
+boost::shared_ptr<Port>
+Push2::output_port()
 {
-       if (what_changed.contains (Properties::selected)) {
-               /* cancel string, which will cause a redraw on the next update
-                * cycle. The redraw will reflect selected status
-                */
-               mid_layout[which]->set_text (string());
+       return _async_out;
+}
+
+boost::shared_ptr<Port>
+Push2::input_port()
+{
+       return _async_in;
+}
+
+int
+Push2::pad_note (int row, int col) const
+{
+       NNPadMap::const_iterator nni = nn_pad_map.find (36+(row*8)+col);
+
+       if (nni != nn_pad_map.end()) {
+               return nni->second->filtered;
        }
+
+       return 0;
 }
 
 void
-Push2::solo_change (int n)
+Push2::update_selection_color ()
 {
-       ButtonID bid;
+       boost::shared_ptr<MidiTrack> current_midi_track = current_pad_target.lock();
 
-       switch (n) {
-       case 0:
-               bid = Upper1;
-               break;
-       case 1:
-               bid = Upper2;
-               break;
-       case 2:
-               bid = Upper3;
-               break;
-       case 3:
-               bid = Upper4;
-               break;
-       case 4:
-               bid = Upper5;
-               break;
-       case 5:
-               bid = Upper6;
-               break;
-       case 6:
-               bid = Upper7;
-               break;
-       case 7:
-               bid = Upper8;
-               break;
-       default:
+       if (!current_midi_track) {
                return;
        }
 
-       boost::shared_ptr<AutomationControl> ac = stripable[n]->solo_control ();
-       if (!ac) {
-               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 ()
+{
+       set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+}
+
+void
+Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
+{
+       MusicalMode m (mode);
+       vector<float>::iterator interval;
+       int note;
+       const int original_root = root;
+
+       interval = m.steps.begin();
+       root += (octave*12);
+       note = root;
+
+       const int root_start = root;
+
+       set<int> mode_map; /* contains only notes in mode, O(logN) lookup */
+       vector<int> mode_vector; /* sorted in note order */
+
+       mode_map.insert (note);
+       mode_vector.push_back (note);
+
+       /* build a map of all notes in the mode, from the root to 127 */
+
+       while (note < 128) {
+
+               if (interval == m.steps.end()) {
+
+                       /* last distance was the end of the scale,
+                          so wrap, adding the next note at one
+                          octave above the last root.
+                       */
+
+                       interval = m.steps.begin();
+                       root += 12;
+                       mode_map.insert (root);
+                       mode_vector.push_back (root);
+
+               } else {
+                       note = (int) floor (root + (2.0 * (*interval)));
+                       interval++;
+                       mode_map.insert (note);
+                       mode_vector.push_back (note);
+               }
        }
 
-       Button* b = id_button_map[bid];
-       if (ac->get_value()) {
-               b->set_color (LED::Red);
+       fn_pad_map.clear ();
+
+       if (inkey) {
+
+               vector<int>::iterator notei;
+               int row_offset = 0;
+
+               for (int row = 0; row < 8; ++row) {
+
+                       /* Ableton's grid layout wraps the available notes in the scale
+                        * by offsetting 3 notes per row (from the bottom)
+                        */
+
+                       notei = mode_vector.begin();
+                       notei += row_offset;
+                       row_offset += 3;
+
+                       for (int col = 0; col < 8; ++col) {
+                               int index = 36 + (row*8) + col;
+                               Pad* pad = nn_pad_map[index];
+                               int notenum;
+                               if (notei != mode_vector.end()) {
+
+                                       notenum = *notei;
+                                       pad->filtered = notenum;
+
+                                       fn_pad_map.insert (make_pair (notenum, pad));
+
+                                       if ((notenum % 12) == original_root) {
+                                               pad->set_color (selection_color);
+                                               pad->perma_color = selection_color;
+                                       } else {
+                                               pad->set_color (LED::White);
+                                               pad->perma_color = LED::White;
+                                       }
+
+                                       pad->do_when_pressed = Pad::FlashOff;
+                                       notei++;
+
+                               } else {
+
+                                       pad->set_color (LED::Black);
+                                       pad->do_when_pressed = Pad::Nothing;
+                                       pad->filtered = -1;
+                               }
+
+                               pad->set_state (LED::OneShot24th);
+                               write (pad->state_msg());
+                       }
+               }
+
        } else {
-               b->set_color (LED::Black);
+
+               /* chromatic: all notes available, but highlight those in the scale */
+
+               for (note = 36; note < 100; ++note) {
+
+                       Pad* pad = nn_pad_map[note];
+
+                       /* Chromatic: all pads play, half-tone steps. Light
+                        * those in the scale, and highlight root notes
+                        */
+
+                       pad->filtered = root_start + (note - 36);
+
+                       fn_pad_map.insert (make_pair (pad->filtered, pad));
+
+                       if (mode_map.find (note) != mode_map.end()) {
+
+                               if ((note % 12) == original_root) {
+                                       pad->set_color (selection_color);
+                                       pad->perma_color = selection_color;
+                               } else {
+                                       pad->set_color (LED::White);
+                                       pad->perma_color = LED::White;
+                               }
+
+                               pad->do_when_pressed = Pad::FlashOff;
+
+                       } else {
+
+                               /* note is not in mode, turn it off */
+
+                               pad->do_when_pressed = Pad::FlashOn;
+                               pad->set_color (LED::Black);
+
+                       }
+
+                       pad->set_state (LED::OneShot24th);
+                       write (pad->state_msg());
+               }
+       }
+
+       /* store state */
+
+       bool changed = false;
+
+       if (_scale_root != original_root) {
+               _scale_root = original_root;
+               changed = true;
+       }
+       if (_root_octave != octave) {
+               _root_octave = octave;
+               changed = true;
+       }
+       if (_in_key != inkey) {
+               _in_key = inkey;
+               changed = true;
+       }
+       if (_mode != mode) {
+               _mode = mode;
+               changed = true;
+       }
+
+       if (changed) {
+               ScaleChange (); /* EMIT SIGNAL */
        }
-       b->set_state (LED::OneShot24th);
-       write (b->state_msg());
 }
 
 void
-Push2::mute_change (int n)
+Push2::set_percussive_mode (bool yn)
 {
-       ButtonID bid;
-
-       switch (n) {
-       case 0:
-               bid = Lower1;
-               break;
-       case 1:
-               bid = Lower2;
-               break;
-       case 2:
-               bid = Lower3;
-               break;
-       case 3:
-               bid = Lower4;
-               break;
-       case 4:
-               bid = Lower5;
-               break;
-       case 5:
-               bid = Lower6;
-               break;
-       case 6:
-               bid = Lower7;
-               break;
-       case 7:
-               bid = Lower8;
-               break;
-       default:
+       if (!yn) {
+               cerr << "back to scale\n";
+               set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+               percussion = false;
                return;
        }
 
-       boost::shared_ptr<AutomationControl> ac = stripable[n]->mute_control ();
-       if (!ac) {
-               return;
+       int drum_note = 36;
+
+       fn_pad_map.clear ();
+
+       for (int row = 0; row < 8; ++row) {
+
+               for (int col = 0; col < 4; ++col) {
+
+                       int index = 36 + (row*8) + col;
+                       Pad* pad = nn_pad_map[index];
+
+                       pad->filtered = drum_note;
+                       drum_note++;
+               }
        }
 
-       Button* b = id_button_map[bid];
+       for (int row = 0; row < 8; ++row) {
 
-       if (ac->get_value ()) {
-               b->set_color (LED::Blue);
-       } else {
-               b->set_color (LED::Black);
+               for (int col = 4; col < 8; ++col) {
+
+                       int index = 36 + (row*8) + col;
+                       Pad* pad = nn_pad_map[index];
+
+                       pad->filtered = drum_note;
+                       drum_note++;
+               }
        }
-       b->set_state (LED::OneShot24th);
-       write (b->state_msg());
+
+       percussion = true;
+}
+
+Push2Layout*
+Push2::current_layout () const
+{
+       Glib::Threads::Mutex::Lock lm (layout_lock);
+       return _current_layout;
 }
 
 void
-Push2::strip_vpot (int n, int delta)
+Push2::stripable_selection_change (StripableNotificationListPtr selected)
 {
-       if (stripable[n]) {
-               boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
-               if (ac) {
-                       ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup);
+       boost::shared_ptr<MidiPort> pad_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->shadow_port();
+       boost::shared_ptr<MidiTrack> current_midi_track = current_pad_target.lock();
+       boost::shared_ptr<MidiTrack> new_pad_target;
+
+       /* See if there's a MIDI track selected */
+
+       for (StripableNotificationList::iterator si = selected->begin(); si != selected->end(); ++si) {
+
+               new_pad_target = boost::dynamic_pointer_cast<MidiTrack> ((*si).lock());
+
+               if (new_pad_target) {
+                       break;
                }
        }
+
+       if (current_midi_track == new_pad_target) {
+               /* nothing to do */
+               return;
+       }
+
+       if (!new_pad_target) {
+               /* leave existing connection alone */
+               return;
+       }
+
+       /* 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);
+       }
+
+       /* now connect the pad port to this (newly) selected midi
+        * track, if indeed there is one.
+        */
+
+       if (new_pad_target && pad_port) {
+               new_pad_target->input()->connect (new_pad_target->input()->nth (0), pad_port->name(), this);
+               current_pad_target = new_pad_target;
+               selection_color = get_color_index (new_pad_target->presentation_info().color());
+               contrast_color = get_color_index (ArdourCanvas::HSV (new_pad_target->presentation_info().color()).opposite().color());
+       } else {
+               current_pad_target.reset ();
+               selection_color = LED::Green;
+               contrast_color = LED::Green;
+       }
+
+       reset_pad_colors ();
 }
 
-void
-Push2::strip_vpot_touch (int n, bool touching)
+Push2::Button*
+Push2::button_by_id (ButtonID bid)
 {
-       if (stripable[n]) {
-               boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
-               if (ac) {
-                       if (touching) {
-                               ac->start_touch (session->audible_frame());
-                       } else {
-                               ac->stop_touch (true, session->audible_frame());
-                       }
-               }
+       return id_button_map[bid];
+}
+
+uint8_t
+Push2::get_color_index (ArdourCanvas::Color rgba)
+{
+       ColorMap::iterator i = color_map.find (rgba);
+
+       if (i != color_map.end()) {
+               return i->second;
+       }
+
+       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 */
+
+       uint8_t index;
+
+       if (color_map_free_list.empty()) {
+               /* random replacement of any entry above zero and below 122 (where the
+                * Ableton standard colors live)
+                */
+               index = 1 + (random() % 121);
+       } else {
+               index = color_map_free_list.top();
+               color_map_free_list.pop();
        }
+
+       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 & 0x80) >> 7;
+       palette_msg[10] = g & 0x7f;
+       palette_msg[11] = (g & 0x80) >> 7;
+       palette_msg[12] = b & 0x7f;
+       palette_msg[13] = (b & 0x80) >> 7;
+       palette_msg[14] = w & 0x7f;
+       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;
+
+       return index;
 }
 
 void
-Push2::other_vpot (int n, int delta)
+Push2::build_color_map ()
 {
-       switch (n) {
-       case 0:
-               break;
-       case 1:
-               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);
-                       }
-               }
-               break;
+       /* These are "standard" colors that Ableton docs suggest will always be
+          there. Put them in our color map so that when we look up these
+          colors, we will use the Ableton indices for them.
+       */
+
+       color_map.insert (make_pair (RGB_TO_UINT (0,0,0), 0));
+       color_map.insert (make_pair (RGB_TO_UINT (204,204,204), 122));
+       color_map.insert (make_pair (RGB_TO_UINT (64,64,64), 123));
+       color_map.insert (make_pair (RGB_TO_UINT (20,20,20), 124));
+       color_map.insert (make_pair (RGB_TO_UINT (0,0,255), 125));
+       color_map.insert (make_pair (RGB_TO_UINT (0,255,0), 126));
+       color_map.insert (make_pair (RGB_TO_UINT (255,0,0), 127));
+
+       for (uint8_t n = 1; n < 122; ++n) {
+               color_map_free_list.push (n);
        }
 }
 
 void
-Push2::other_vpot_touch (int n, bool touching)
+Push2::fill_color_table ()
 {
-       switch (n) {
-       case 0:
-               break;
-       case 1:
-               break;
-       case 2:
-               if (master) {
-                       boost::shared_ptr<AutomationControl> ac = master->gain_control();
-                       if (ac) {
-                               if (touching) {
-                                       ac->start_touch (session->audible_frame());
-                               } else {
-                                       ac->stop_touch (true, session->audible_frame());
-                               }
-                       }
-               }
+       colors.insert (make_pair (DarkBackground, ArdourCanvas::rgba_to_color (0, 0, 0, 1)));
+       colors.insert (make_pair (LightBackground, ArdourCanvas::rgba_to_color (0.98, 0.98, 0.98, 1)));
+
+       colors.insert (make_pair (ParameterName, ArdourCanvas::rgba_to_color (0.98, 0.98, 0.98, 1)));
+
+       colors.insert (make_pair (KnobArcBackground, ArdourCanvas::rgba_to_color (0.3, 0.3, 0.3, 1.0)));
+       colors.insert (make_pair (KnobArcStart, ArdourCanvas::rgba_to_color (1.0, 0.0, 0.0, 1.0)));
+       colors.insert (make_pair (KnobArcEnd, ArdourCanvas::rgba_to_color (0.0, 1.0, 0.0, 1.0)));
+
+       colors.insert (make_pair (KnobLineShadow, ArdourCanvas::rgba_to_color  (0, 0, 0, 0.3)));
+       colors.insert (make_pair (KnobLine, ArdourCanvas::rgba_to_color (1, 1, 1, 1)));
+
+       colors.insert (make_pair (KnobForeground, ArdourCanvas::rgba_to_color (0.2, 0.2, 0.2, 1)));
+       colors.insert (make_pair (KnobBackground, ArdourCanvas::rgba_to_color (0.2, 0.2, 0.2, 1)));
+       colors.insert (make_pair (KnobShadow, ArdourCanvas::rgba_to_color (0, 0, 0, 0.1)));
+       colors.insert (make_pair (KnobBorder, ArdourCanvas::rgba_to_color (0, 0, 0, 1)));
+
+}
+
+ArdourCanvas::Color
+Push2::get_color (ColorName name)
+{
+       Colors::iterator c = colors.find (name);
+       if (c != colors.end()) {
+               return c->second;
        }
+
+       return random();
 }
 
 void
-Push2::start_shift ()
+Push2::set_current_layout (Push2Layout* layout)
 {
-       cerr << "start shift\n";
-       modifier_state = ModifierState (modifier_state | ModShift);
-       Button* b = id_button_map[Shift];
-       b->set_color (LED::White);
-       b->set_state (LED::Blinking16th);
-       write (b->state_msg());
+       if (layout && layout == _current_layout) {
+               _current_layout->show ();
+       } else {
+
+               if (_current_layout) {
+                       _current_layout->hide ();
+                       _canvas->root()->remove (_current_layout);
+                       _previous_layout = _current_layout;
+               }
+
+               _current_layout = layout;
+
+               if (_current_layout) {
+                       _canvas->root()->add (_current_layout);
+                       _current_layout->show ();
+               }
+
+
+               _canvas->request_redraw ();
+       }
 }
 
 void
-Push2::end_shift ()
+Push2::use_previous_layout ()
 {
-       if (modifier_state & ModShift) {
-               cerr << "end shift\n";
-               modifier_state = ModifierState (modifier_state & ~(ModShift));
-               Button* b = id_button_map[Shift];
-               b->timeout_connection.disconnect ();
-               b->set_color (LED::White);
-               b->set_state (LED::OneShot24th);
-               write (b->state_msg());
+       if (_previous_layout) {
+               set_current_layout (_previous_layout);
        }
 }
 
 void
-Push2::start_select ()
+Push2::request_pressure_mode ()
 {
-       cerr << "start select\n";
-       modifier_state = ModifierState (modifier_state | ModSelect);
-       Button* b = id_button_map[Select];
-       b->set_color (LED::White);
-       b->set_state (LED::Blinking16th);
-       write (b->state_msg());
+       MidiByteArray msg (8, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01, 0x1F, 0xF7);
+       write (msg);
 }
 
 void
-Push2::end_select ()
+Push2::set_pressure_mode (PressureMode pm)
 {
-       if (modifier_state & ModSelect) {
-               cerr << "end select\n";
-               modifier_state = ModifierState (modifier_state & ~(ModSelect));
-               Button* b = id_button_map[Select];
-               b->timeout_connection.disconnect ();
-               b->set_color (LED::White);
-               b->set_state (LED::OneShot24th);
-               write (b->state_msg());
+       MidiByteArray msg (9, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01, 0x1E, 0x0, 0xF7);
+
+       switch (pm) {
+       case AfterTouch:
+               /* nothing to do, message is correct */
+               break;
+       case PolyPressure:
+               msg[7] = 0x1;
+               break;
+       default:
+               return;
        }
+
+       write (msg);
+       cerr << "Sent PM message " << msg << endl;
 }