push2: parameterize and centralize colors
[ardour.git] / libs / surfaces / push2 / push2.cc
index d2e4be3fdee639a9adc3b59faa1b75118bc86cf5..c187dc77aa0931058c772e05c964c35599a938df 100644 (file)
@@ -1,24 +1,22 @@
 /*
-       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 "ardour/session.h"
 #include "ardour/tempo.h"
 
+#include "gtkmm2ext/rgb_macros.h"
+
+#include "canvas/colors.h"
+
 #include "push2.h"
 #include "gui.h"
+#include "layout.h"
+#include "scale.h"
+#include "mix.h"
+#include "track_mix.h"
 #include "menu.h"
 
+#include "i18n.h"
+
 using namespace ARDOUR;
 using namespace std;
 using namespace PBD;
 using namespace Glib;
 using namespace ArdourSurface;
 
-#include "i18n.h"
-
 #include "pbd/abstract_ui.cc" // instantiate template
 
 const int Push2::cols = 960;
@@ -122,9 +128,10 @@ Push2::Push2 (ARDOUR::Session& s)
        , handle (0)
        , device_buffer (0)
        , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, cols, rows))
-       , modifier_state (None)
+       , _modifier_state (None)
        , splash_start (0)
-       , bank_start (0)
+       , _current_layout (0)
+       , drawn_layout (0)
        , connection_state (ConnectionState (0))
        , gui (0)
        , _mode (MusicalMode::IonianMajor)
@@ -133,42 +140,27 @@ Push2::Push2 (ARDOUR::Session& s)
        , _in_key (true)
        , octave_shift (0)
        , percussion (false)
-       , current_menu (0)
-       , drawn_menu (0)
 {
        context = Cairo::Context::create (frame_buffer);
-       tc_clock_layout = Pango::Layout::create (context);
-       bbt_clock_layout = Pango::Layout::create (context);
 
-       Pango::FontDescription fd ("Sans Bold 24");
-       tc_clock_layout->set_font_description (fd);
-       bbt_clock_layout->set_font_description (fd);
-
-       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");
-       }
-
-       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);
-       }
-
-       build_pad_table ();
        build_maps ();
-       build_scale_menu ();
+       build_color_map ();
+       fill_color_table (); 
+
+       /* master cannot be removed, so no need to connect to going-away signal */
+       master = session->master_out ();
 
        if (open ()) {
                throw failed_constructor ();
        }
 
-       StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
+       ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
+
+       /* catch current selection, if any */
+       {
+               StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
+               stripable_selection_change (sp);
+       }
 
        /* 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);
@@ -281,6 +273,12 @@ Push2::open ()
 
        connect_to_parser ();
 
+       mix_layout = new MixLayout (*this, *session, context);
+       scale_layout = new ScaleLayout (*this, *session, context);
+       track_mix_layout = new TrackMixLayout (*this, *session, context);
+
+       _current_layout = mix_layout;
+
        return 0;
 }
 
@@ -317,7 +315,13 @@ Push2::close ()
        vblank_connection.disconnect ();
        periodic_connection.disconnect ();
        session_connections.drop_connections ();
-       stripable_connections.drop_connections ();
+
+       _current_layout = 0;
+       drawn_layout = 0;
+       delete mix_layout;
+       mix_layout = 0;
+       delete scale_layout;
+       scale_layout = 0;
 
        if (handle) {
                libusb_release_interface (handle, 0x00);
@@ -325,10 +329,6 @@ Push2::close ()
                handle = 0;
        }
 
-       for (int n = 0; n < 8; ++n) {
-               stripable[n].reset ();
-       }
-
        delete [] device_frame_buffer;
        device_frame_buffer = 0;
 
@@ -509,133 +509,34 @@ Push2::blit_to_device_frame_buffer ()
 bool
 Push2::redraw ()
 {
-       string tc_clock_text;
-       string bbt_clock_text;
-
        if (splash_start) {
-               if (get_microseconds() - splash_start > 4000000) {
-                       splash_start = 0;
-               } else {
-                       return false;
-               }
-       }
-
-       if (current_menu) {
-               if (current_menu->dirty() || drawn_menu != current_menu) {
-                       /* fill background */
-                       context->set_source_rgb (0.764, 0.882, 0.882);
-                       context->rectangle (0, 0, 960, 160);
-                       context->fill ();
-                       /* now menu */
-                       current_menu->redraw (context);
-                       drawn_menu = current_menu;
-                       return true;
-               }
-               return false;
-       } else {
-               drawn_menu = 0;
-       }
-
-       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;
+               /* display splash for 3 seconds */
 
-               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);
+               if (get_microseconds() - splash_start > 3000000) {
+                       splash_start = 0;
                } else {
-                       snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
-                                 bbt.bars, bbt.beats, bbt.ticks);
+                       return false;
                }
-
-               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;
+       Glib::Threads::Mutex::Lock lm (layout_lock, Glib::Threads::TRY_LOCK);
 
-       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) {
+       if (!lm.locked()) {
+               /* can't get layout, no re-render needed */
                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);
-       }
-
-       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);
-       }
-
-       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();
-               }
-               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);
+       bool render_needed = false;
+
+       if (drawn_layout != _current_layout) {
+               render_needed = true;
        }
 
-       /* render clock */
-       /* render foo */
-       /* render bar */
+       bool dirty = _current_layout->redraw (context);
+       drawn_layout = _current_layout;
 
-       return true;
+       return dirty || render_needed;
 }
 
 bool
@@ -709,14 +610,8 @@ Push2::set_active (bool yn)
                init_buttons (true);
                init_touch_strip ();
                set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
-               switch_bank (0);
                splash ();
 
-               /* catch current selection, if any */
-               {
-                       StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
-                       stripable_selection_change (sp);
-               }
 
        } else {
 
@@ -861,33 +756,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);
@@ -913,28 +808,28 @@ Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* e
 
        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 */
@@ -1018,16 +913,6 @@ Push2::handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
 void
 Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb)
 {
-       if (!session) {
-               return;
-       }
-
-       float speed;
-
-       /* 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
@@ -1233,317 +1118,6 @@ Push2::set_state (const XMLNode & node, int version)
        return retval;
 }
 
-void
-Push2::switch_bank (uint32_t base)
-{
-       if (!session) {
-               return;
-       }
-
-       stripable_connections.drop_connections ();
-
-       /* try to get the first stripable for the requested bank */
-
-       stripable[0] = session->get_remote_nth_stripable (base, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
-
-       if (!stripable[0]) {
-               return;
-       }
-
-       /* at least one stripable in this bank */
-       bank_start = base;
-
-       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));
-
-
-       for (int n = 0; n < 8; ++n) {
-               if (!stripable[n]) {
-                       continue;
-               }
-
-               /* stripable goes away? refill the bank, starting at the same point */
-
-               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);
-               }
-
-               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);
-               }
-
-               stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_property_change, this, _1, n), this);
-
-               solo_change (n);
-               mute_change (n);
-
-       }
-
-       /* master cannot be removed, so no need to connect to going-away signal */
-       master = session->master_out ();
-}
-
-void
-Push2::stripable_property_change (PropertyChange const& what_changed, int which)
-{
-       if (what_changed.contains (Properties::selected)) {
-               if (!stripable[which]) {
-                       return;
-               }
-
-               /* cancel string, which will cause a redraw on the next update
-                * cycle. The redraw will reflect selected status
-                */
-
-               mid_layout[which]->set_text (string());
-       }
-}
-
-void
-Push2::stripable_selection_change (StripableNotificationListPtr selected)
-{
-
-       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 (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;
-       }
-
-       if (!new_pad_target) {
-               /* leave existing connection alone */
-               return;
-       }
-
-       /* disconnect from pad port, if appropriate */
-
-       if (current_midi_track && pad_port) {
-               cerr << "Disconnect pads from " << current_midi_track->name() << endl;
-               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) {
-               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;
-       } else {
-               current_pad_target.reset ();
-       }
-}
-
-
-void
-Push2::solo_change (int n)
-{
-       ButtonID bid;
-
-       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:
-               return;
-       }
-
-       boost::shared_ptr<SoloControl> ac = stripable[n]->solo_control ();
-       if (!ac) {
-               return;
-       }
-
-       Button* b = id_button_map[bid];
-
-       if (ac->soloed()) {
-               b->set_color (LED::Green);
-       } else {
-               b->set_color (LED::Black);
-       }
-
-       if (ac->soloed_by_others_upstream() || ac->soloed_by_others_downstream()) {
-               b->set_state (LED::Blinking4th);
-       } else {
-               b->set_state (LED::OneShot24th);
-       }
-
-       write (b->state_msg());
-}
-
-void
-Push2::mute_change (int n)
-{
-       ButtonID bid;
-
-       if (!stripable[n]) {
-               return;
-       }
-
-       cerr << "Mute changed on " << n << ' ' << stripable[n]->name() << endl;
-
-       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:
-               return;
-       }
-
-       boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
-
-       if (!mc) {
-               return;
-       }
-
-       Button* b = id_button_map[bid];
-
-       if (Config->get_show_solo_mutes() && !Config->get_solo_control_is_listen_control ()) {
-
-               if (mc->muted_by_self ()) {
-                       /* full mute */
-                       b->set_color (LED::Blue);
-                       b->set_state (LED::OneShot24th);
-                       cerr << "FULL MUTE1\n";
-               } else if (mc->muted_by_others_soloing () || mc->muted_by_masters ()) {
-                       /* this will reflect both solo mutes AND master mutes */
-                       b->set_color (LED::Blue);
-                       b->set_state (LED::Blinking4th);
-                       cerr << "OTHER MUTE1\n";
-               } else {
-                       /* no mute at all */
-                       b->set_color (LED::Black);
-                       b->set_state (LED::OneShot24th);
-                       cerr << "NO MUTE1\n";
-               }
-
-       } else {
-
-               if (mc->muted_by_self()) {
-                       /* full mute */
-                       b->set_color (LED::Blue);
-                       b->set_state (LED::OneShot24th);
-                       cerr << "FULL MUTE2\n";
-               } else if (mc->muted_by_masters ()) {
-                       /* this shows only master mutes, not mute-by-others-soloing */
-                       b->set_color (LED::Blue);
-                       b->set_state (LED::Blinking4th);
-                       cerr << "OTHER MUTE1\n";
-               } else {
-                       /* no mute at all */
-                       b->set_color (LED::Black);
-                       b->set_state (LED::OneShot24th);
-                       cerr << "NO MUTE2\n";
-               }
-       }
-
-       write (b->state_msg());
-}
-
-void
-Push2::strip_vpot (int n, int delta)
-{
-       if (current_menu) {
-               current_menu->step_active (n, delta);
-               return;
-       }
-
-       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);
-               }
-       }
-}
-
-void
-Push2::strip_vpot_touch (int n, bool touching)
-{
-       if (current_menu) {
-               return;
-       }
-
-       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());
-                       }
-               }
-       }
-}
-
 void
 Push2::other_vpot (int n, int delta)
 {
@@ -1590,7 +1164,7 @@ void
 Push2::start_shift ()
 {
        cerr << "start shift\n";
-       modifier_state = ModifierState (modifier_state | ModShift);
+       _modifier_state = ModifierState (_modifier_state | ModShift);
        Button* b = id_button_map[Shift];
        b->set_color (LED::White);
        b->set_state (LED::Blinking16th);
@@ -1600,9 +1174,9 @@ Push2::start_shift ()
 void
 Push2::end_shift ()
 {
-       if (modifier_state & ModShift) {
+       if (_modifier_state & ModShift) {
                cerr << "end shift\n";
-               modifier_state = ModifierState (modifier_state & ~(ModShift));
+               _modifier_state = ModifierState (_modifier_state & ~(ModShift));
                Button* b = id_button_map[Shift];
                b->timeout_connection.disconnect ();
                b->set_color (LED::White);
@@ -1611,31 +1185,6 @@ Push2::end_shift ()
        }
 }
 
-void
-Push2::start_select ()
-{
-       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());
-}
-
-void
-Push2::end_select ()
-{
-       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());
-       }
-}
-
 void
 Push2::splash ()
 {
@@ -1720,7 +1269,7 @@ Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
                                        Pad const * pad = nni->second;
                                        /* shift for output to the shadow port */
                                        if (pad->filtered >= 0) {
-                                               (*ev).set_note (pad->filtered);
+                                               (*ev).set_note (pad->filtered + (octave_shift*12));
                                                out.push_back (*ev);
                                                /* shift back so that the pads light correctly  */
                                                (*ev).set_note (n);
@@ -1804,16 +1353,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
 {
@@ -2005,72 +1544,171 @@ Push2::set_percussive_mode (bool yn)
        PadChange (); /* EMIT SIGNAL */
 }
 
-void
-Push2::set_menu (Push2Menu* m)
+Push2Layout*
+Push2::current_layout () const
 {
-       current_menu = m;
-       drawn_menu = 0;
+       Glib::Threads::Mutex::Lock lm (layout_lock);
+       return _current_layout;
 }
 
 void
-Push2::build_scale_menu ()
+Push2::stripable_selection_change (StripableNotificationListPtr selected)
 {
-       vector<string> v;
-
-       scale_menu = new Push2Menu (context);
-
-       v.push_back ("Dorian");
-       v.push_back ("IonianMajor");
-       v.push_back ("Minor");
-       v.push_back ("HarmonicMinor");
-       v.push_back ("MelodicMinorAscending");
-       v.push_back ("MelodicMinorDescending");
-       v.push_back ("Phrygian");
-       v.push_back ("Lydian");
-       v.push_back ("Mixolydian");
-       v.push_back ("Aeolian");
-       v.push_back ("Locrian");
-       v.push_back ("PentatonicMajor");
-       v.push_back ("PentatonicMinor");
-       v.push_back ("Chromatic");
-       v.push_back ("BluesScale");
-       v.push_back ("NeapolitanMinor");
-       v.push_back ("NeapolitanMajor");
-       v.push_back ("Oriental");
-       v.push_back ("DoubleHarmonic");
-       v.push_back ("Enigmatic");
-       v.push_back ("Hirajoshi");
-       v.push_back ("HungarianMinor");
-       v.push_back ("HungarianMajor");
-       v.push_back ("Kumoi");
-       v.push_back ("Iwato");
-       v.push_back ("Hindu");
-       v.push_back ("Spanish8Tone");
-       v.push_back ("Pelog");
-       v.push_back ("HungarianGypsy");
-       v.push_back ("Overtone");
-       v.push_back ("LeadingWholeTone");
-       v.push_back ("Arabian");
-       v.push_back ("Balinese");
-       v.push_back ("Gypsy");
-       v.push_back ("Mohammedan");
-       v.push_back ("Javanese");
-       v.push_back ("Persian");
-       v.push_back ("Algeria");
-
-       scale_menu->fill_column (0, v);
-
-       v.clear ();
+       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 (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;
+       }
+
+       if (!new_pad_target) {
+               /* leave existing connection alone */
+               return;
+       }
+
+       /* disconnect from pad port, if appropriate */
+
+       if (current_midi_track && pad_port) {
+               cerr << "Disconnect pads from " << current_midi_track->name() << endl;
+               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) {
+               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;
+       } else {
+               current_pad_target.reset ();
+       }
+}
+
+Push2::Button*
+Push2::button_by_id (ButtonID bid)
+{
+       return id_button_map[bid];
+}
+
+uint8_t
+Push2::get_color_index (uint32_t rgb)
+{
+       ColorMap::iterator i = color_map.find (rgb);
+
+       if (i != color_map.end()) {
+               return i->second;
+       }
+
+       int r, g, b, a;
+       UINT_TO_RGBA (rgb, &r, &g, &b, &a);
+       int w = 204; /* not sure where/when we should get this value */
+
+       /* 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, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x01, 0x7E, 0x00, 0xF7);
+       MidiByteArray update_pallette_msg (8, 0xf0, 0x00, 0x21, 0x1d, 0x01, 0x01, 0x05, 0xF7);
+
+       palette_msg[7] = index;
+       palette_msg[8] = r & 0x7f;
+       palette_msg[9] = r & 0x1;
+       palette_msg[10] = g & 0x7f;
+       palette_msg[11] = g & 0x1;
+       palette_msg[12] = b & 0x7f;
+       palette_msg[13] = b & 0x1;
+       palette_msg[14] = w & 0x7f;
+       palette_msg[15] = w & 0x1;
+
+       write (palette_msg);
+       write (update_pallette_msg);
+
+       color_map[index] = rgb;
+
+       return index;
 }
 
 void
-Push2::show_scale_menu ()
+Push2::build_color_map ()
 {
-       set_menu (scale_menu);
+       /* 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::cancel_menu ()
+Push2::fill_color_table ()
 {
-       set_menu (0);
+       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.32, 0.28, 0.47, 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 (1, 1, 1, 1)));
+       colors.insert (make_pair (KnobBackground, ArdourCanvas::rgba_to_color (1, 1, 1, 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)));
+
+}
+
+uint32_t
+Push2::get_color (ColorName name)
+{
+       Colors::iterator c = colors.find (name);
+       if (c != colors.end()) {
+               return c->second;
+       }
+
+       return random();
 }