move mode/scale/key definitions out of push2 code and into libardour; Aeolian is...
[ardour.git] / libs / surfaces / push2 / push2.cc
index a293c58c6456e1708c1c47d2dd97115ac98ba120..73c871548b070d8effdf66993f9133739550d932 100644 (file)
@@ -16,6 +16,8 @@
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
+#include <stdlib.h>
+
 #include "pbd/compose.h"
 #include "pbd/convert.h"
 #include "pbd/debug.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/filesystem_paths.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 "push2.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 "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace ARDOUR;
 using namespace std;
@@ -52,10 +65,6 @@ using namespace ArdourSurface;
 
 #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
 
@@ -66,62 +75,20 @@ register_enums ()
        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)
-       , device_buffer (0)
-       , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, cols, rows))
        , _modifier_state (None)
        , splash_start (0)
        , _current_layout (0)
-       , drawn_layout (0)
+       , _previous_layout (0)
        , connection_state (ConnectionState (0))
        , gui (0)
        , _mode (MusicalMode::IonianMajor)
@@ -130,11 +97,14 @@ Push2::Push2 (ARDOUR::Session& s)
        , _in_key (true)
        , octave_shift (0)
        , percussion (false)
+       , _pressure_mode (AfterTouch)
+       , selection_color (LED::Green)
+       , contrast_color (LED::Green)
 {
-       context = Cairo::Context::create (frame_buffer);
 
-       build_pad_table ();
        build_maps ();
+       build_color_map ();
+       fill_color_table ();
 
        /* master cannot be removed, so no need to connect to going-away signal */
        master = session->master_out ();
@@ -164,11 +134,20 @@ Push2::Push2 (ARDOUR::Session& s)
 Push2::~Push2 ()
 {
        stop ();
+
+       delete track_mix_layout;
+       delete mix_layout;
+       delete scale_layout;
 }
 
 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;
@@ -212,15 +191,19 @@ Push2::open ()
                return -1;
        }
 
-       device_frame_buffer = new uint16_t[rows*pixels_per_row];
-
-       memset (device_frame_buffer, 0, sizeof (uint16_t) * rows * pixels_per_row);
-
-       frame_header[0] = 0xef;
-       frame_header[1] = 0xcd;
-       frame_header[2] = 0xab;
-       frame_header[3] = 0x89;
-       memset (&frame_header[4], 0, 12);
+       try {
+               _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");
+       } catch (...) {
+               error << _("Cannot construct Canvas for display") << endmsg;
+               libusb_release_interface (handle, 0x00);
+               libusb_close (handle);
+               handle = 0;
+               return -1;
+       }
 
        /* setup ports */
 
@@ -262,10 +245,6 @@ Push2::open ()
 
        connect_to_parser ();
 
-       mix_layout = new MixLayout (*this, *session, context);
-       scale_layout = new ScaleLayout (*this, *session, context);
-       _current_layout = mix_layout;
-
        return 0;
 }
 
@@ -285,6 +264,7 @@ int
 Push2::close ()
 {
        init_buttons (false);
+       strip_buttons_off ();
 
        /* wait for button data to be flushed */
        AsyncMIDIPort* asp;
@@ -299,16 +279,20 @@ Push2::close ()
        _input_port = 0;
        _output_port = 0;
 
-       vblank_connection.disconnect ();
        periodic_connection.disconnect ();
        session_connections.drop_connections ();
 
-       _current_layout = 0;
-       drawn_layout = 0;
+       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;
 
        if (handle) {
                libusb_release_interface (handle, 0x00);
@@ -316,12 +300,25 @@ Push2::close ()
                handle = 0;
        }
 
-       delete [] device_frame_buffer;
-       device_frame_buffer = 0;
-
        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)
 {
@@ -330,7 +327,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
        };
 
@@ -346,21 +343,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
-        */
-
-       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) */
@@ -419,7 +401,6 @@ 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);
@@ -438,113 +419,34 @@ Push2::stop ()
        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 ()
 {
        if (splash_start) {
 
-               /* display splash for 3 seconds */
+               /* display splash for 2 seconds */
 
-               if (get_microseconds() - splash_start > 3000000) {
+               if (get_microseconds() - splash_start > 2000000) {
                        splash_start = 0;
-               } else {
-                       return false;
+                       DEBUG_TRACE (DEBUG::Push2, "splash interval ended, switch to mix layout\n");
+                       set_current_layout (mix_layout);
                }
        }
 
-       Glib::Threads::Mutex::Lock lm (layout_lock, Glib::Threads::TRY_LOCK);
-
-       if (!lm.locked()) {
-               /* can't get layout, no re-render needed */
-               return false;
-       }
-
-       bool render_needed = false;
-
-       if (drawn_layout != _current_layout) {
-               render_needed = true;
-       }
-
-       bool dirty = _current_layout->redraw (context);
-       drawn_layout = _current_layout;
-
-       return dirty || render_needed;
-}
-
-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;
 }
@@ -599,7 +501,6 @@ Push2::set_active (bool yn)
                set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
                splash ();
 
-
        } else {
 
                stop ();
@@ -688,6 +589,31 @@ 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 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;
+       }
 }
 
 void
@@ -786,7 +712,7 @@ Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
 void
 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);
@@ -844,22 +770,26 @@ Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* e
                return;
        }
 
-       /* Pad */
+       /* Pad illuminations */
 
-       NNPadMap::iterator pi = nn_pad_map.find (ev->note_number);
+       NNPadMap::const_iterator pm = nn_pad_map.find (ev->note_number);
 
-       if (pi == nn_pad_map.end()) {
+       if (pm == nn_pad_map.end()) {
                return;
        }
 
-       Pad* pad = pi->second;
+       const Pad * const pad_pressed = pm->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 (LED::Black);
+       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());
        }
@@ -868,7 +798,7 @@ Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* e
 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));
 
        if (ev->note_number < 11) {
                /* theoretically related to encoder touch start/end, but
@@ -878,22 +808,34 @@ Push2::handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
                return;
        }
 
-       NNPadMap::iterator pi = nn_pad_map.find (ev->note_number);
+       /* Pad illuminations */
 
-       if (pi == nn_pad_map.end()) {
+       NNPadMap::const_iterator pm = nn_pad_map.find (ev->note_number);
+
+       if (pm == nn_pad_map.end()) {
                return;
        }
 
-       Pad* pad = pi->second;
+       const Pad * const pad_pressed = pm->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());
+       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());
+               }
        }
 }
 
@@ -1108,17 +1050,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;
@@ -1172,65 +1128,6 @@ Push2::end_shift ()
        }
 }
 
-void
-Push2::splash ()
-{
-       std::string splash_file;
-
-       Searchpath rc (ARDOUR::ardour_data_search_path());
-       rc.add_subdirectory_to_paths ("resources");
-
-       if (!find_file (rc, PROGRAM_NAME "-splash.png", splash_file)) {
-               cerr << "Cannot find splash screen image file\n";
-               throw failed_constructor();
-       }
-
-       Cairo::RefPtr<Cairo::ImageSurface> img = Cairo::ImageSurface::create_from_png (splash_file);
-
-       double x_ratio = (double) img->get_width() / (cols - 20);
-       double y_ratio = (double) img->get_height() / (rows - 20);
-       double scale = min (x_ratio, y_ratio);
-
-       /* background */
-
-       context->set_source_rgb (0.764, 0.882, 0.882);
-       context->paint ();
-
-       /* image */
-
-       context->save ();
-       context->translate (5, 5);
-       context->scale (scale, scale);
-       context->set_source (img, 0, 0);
-       context->paint ();
-       context->restore ();
-
-       /* text */
-
-       Glib::RefPtr<Pango::Layout> some_text = Pango::Layout::create (context);
-
-       Pango::FontDescription fd ("Sans 38");
-       some_text->set_font_description (fd);
-       some_text->set_text (string_compose ("%1 %2", PROGRAM_NAME, VERSIONSTRING));
-
-       context->move_to (200, 10);
-       context->set_source_rgb (0, 0, 0);
-       some_text->update_from_cairo_context (context);
-       some_text->show_in_cairo_context (context);
-
-       Pango::FontDescription fd2 ("Sans Italic 18");
-       some_text->set_font_description (fd2);
-       some_text->set_text (_("Ableton Push 2 Support"));
-
-       context->move_to (200, 80);
-       context->set_source_rgb (0, 0, 0);
-       some_text->update_from_cairo_context (context);
-       some_text->show_in_cairo_context (context);
-
-       splash_start = get_microseconds ();
-       blit_to_device_frame_buffer ();
-}
-
 bool
 Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
 {
@@ -1269,7 +1166,7 @@ Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
 
                                matched = true;
                        }
-               } else if ((*ev).is_pitch_bender() || (*ev).is_aftertouch() || (*ev).is_channel_pressure()) {
+               } else if ((*ev).is_pitch_bender() || (*ev).is_poly_pressure() || (*ev).is_channel_pressure()) {
                        out.push_back (*ev);
                }
        }
@@ -1315,7 +1212,7 @@ 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 ();
+                connected ();
 
        } else {
                DEBUG_TRACE (DEBUG::FaderPort, "Device disconnected (input or output or both) or not yet fully connected\n");
@@ -1328,6 +1225,12 @@ 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()
 {
@@ -1340,16 +1243,6 @@ Push2::input_port()
        return _async_in;
 }
 
-void
-Push2::build_pad_table ()
-{
-       for (int n = 36; n < 100; ++n) {
-               pad_map[n] = n + (octave_shift*12);
-       }
-
-       PadChange (); /* emit signal */
-}
-
 int
 Push2::pad_note (int row, int col) const
 {
@@ -1362,6 +1255,27 @@ 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 ()
+{
+       set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+}
+
 void
 Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
 {
@@ -1406,10 +1320,13 @@ Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
                }
        }
 
+       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
@@ -1429,9 +1346,11 @@ Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
                                        notenum = *notei;
                                        pad->filtered = notenum;
 
+                                       fn_pad_map.insert (make_pair (notenum, pad));
+
                                        if ((notenum % 12) == original_root) {
-                                               pad->set_color (LED::Green);
-                                               pad->perma_color = LED::Green;
+                                               pad->set_color (selection_color);
+                                               pad->perma_color = selection_color;
                                        } else {
                                                pad->set_color (LED::White);
                                                pad->perma_color = LED::White;
@@ -1447,6 +1366,7 @@ Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
                                        pad->filtered = -1;
                                }
 
+                               pad->set_state (LED::OneShot24th);
                                write (pad->state_msg());
                        }
                }
@@ -1465,11 +1385,13 @@ Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
 
                        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 (LED::Green);
-                                       pad->perma_color = LED::Green;
+                                       pad->set_color (selection_color);
+                                       pad->perma_color = selection_color;
                                } else {
                                        pad->set_color (LED::White);
                                        pad->perma_color = LED::White;
@@ -1486,18 +1408,35 @@ Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
 
                        }
 
+                       pad->set_state (LED::OneShot24th);
                        write (pad->state_msg());
                }
        }
 
-       PadChange (); /* EMIT SIGNAL */
-
        /* store state */
 
-       _scale_root = original_root;
-       _root_octave = octave;
-       _in_key = inkey;
-       _mode = mode;
+       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 */
+       }
 }
 
 void
@@ -1512,6 +1451,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) {
@@ -1537,8 +1478,6 @@ Push2::set_percussive_mode (bool yn)
        }
 
        percussion = true;
-
-       PadChange (); /* EMIT SIGNAL */
 }
 
 Push2Layout*
@@ -1566,12 +1505,6 @@ Push2::stripable_selection_change (StripableNotificationListPtr selected)
                }
        }
 
-       if (new_pad_target) {
-               cerr << "new midi pad target " << new_pad_target->name() << endl;
-       } else {
-               cerr << "no midi pad target\n";
-       }
-
        if (current_midi_track == new_pad_target) {
                /* nothing to do */
                return;
@@ -1585,7 +1518,14 @@ Push2::stripable_selection_change (StripableNotificationListPtr selected)
        /* disconnect from pad port, if appropriate */
 
        if (current_midi_track && pad_port) {
-               cerr << "Disconnect pads from " << current_midi_track->name() << endl;
+
+               /* 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);
        }
 
@@ -1594,12 +1534,17 @@ Push2::stripable_selection_change (StripableNotificationListPtr selected)
         */
 
        if (new_pad_target && pad_port) {
-               cerr << "Reconnect pads to " << new_pad_target->name() << endl;
                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 ();
 }
 
 Push2::Button*
@@ -1607,3 +1552,179 @@ Push2::button_by_id (ButtonID bid)
 {
        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::build_color_map ()
+{
+       /* 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::fill_color_table ()
+{
+       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::set_current_layout (Push2Layout* layout)
+{
+       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::use_previous_layout ()
+{
+       if (_previous_layout) {
+               set_current_layout (_previous_layout);
+       }
+}
+
+void
+Push2::request_pressure_mode ()
+{
+       MidiByteArray msg (8, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01, 0x1F, 0xF7);
+       write (msg);
+}
+
+void
+Push2::set_pressure_mode (PressureMode pm)
+{
+       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;
+}