additional i18n.h changes for push2 branch
[ardour.git] / libs / surfaces / push2 / push2.cc
index d5672fe886558321bbc27e1b4d6ce568561e5a4d..0544446a311ac0304fca87c8c35f4cd5eef97a6c 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/gui_thread.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 "pbd/i18n.h"
 
 using namespace ARDOUR;
 using namespace std;
@@ -51,8 +61,6 @@ using namespace PBD;
 using namespace Glib;
 using namespace ArdourSurface;
 
-#include "i18n.h"
-
 #include "pbd/abstract_ui.cc" // instantiate template
 
 const int Push2::cols = 960;
@@ -121,49 +129,39 @@ 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)
-       , scale_root (36)
-       , root_octave (3)
-       , in_key (true)
+       , _mode (MusicalMode::IonianMajor)
+       , _scale_root (0)
+       , _root_octave (3)
+       , _in_key (true)
        , octave_shift (0)
+       , percussion (false)
 {
        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_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);
@@ -178,11 +176,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;
@@ -238,23 +245,63 @@ Push2::open ()
 
        /* 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) {
                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.
+        */
+
        _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
        _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
 
-       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));
+       /* 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 ();
 
+       mix_layout = new MixLayout (*this, *session, context);
+       scale_layout = new ScaleLayout (*this, *session, context);
+       track_mix_layout = new TrackMixLayout (*this, *session, context);
+
        return 0;
 }
 
+list<boost::shared_ptr<ARDOUR::Bundle> >
+Push2::bundles ()
+{
+       list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+       if (_output_bundle) {
+               b.push_back (_output_bundle);
+       }
+
+       return b;
+}
+
 int
 Push2::close ()
 {
@@ -276,7 +323,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);
@@ -284,10 +337,6 @@ Push2::close ()
                handle = 0;
        }
 
-       for (int n = 0; n < 8; ++n) {
-               stripable[n].reset ();
-       }
-
        delete [] device_frame_buffer;
        device_frame_buffer = 0;
 
@@ -303,7 +352,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,
-                              Quantize, Duplicate, Browse, PageRight, PageLeft, OctaveUp, OctaveDown
+                              Quantize, Duplicate, Browse, PageRight, PageLeft, OctaveUp, OctaveDown, Layout, Scale
        };
 
        for (size_t n = 0; n < sizeof (buttons) / sizeof (buttons[0]); ++n) {
@@ -339,7 +388,7 @@ Push2::init_buttons (bool startup)
 
                ButtonID off_buttons[] = { TapTempo, Setup, User, Stop, Convert, New, FixedLength,
                                           Fwd32ndT, Fwd32nd, Fwd16thT, Fwd16th, Fwd8thT, Fwd8th, Fwd4trT, Fwd4tr,
-                                          Accent, Scale, Layout, Note, Session,  };
+                                          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]];
@@ -468,117 +517,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 (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);
+               /* display splash for 3 seconds */
 
-               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);
+               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);
-       }
+       Glib::Threads::Mutex::Lock lm (layout_lock, Glib::Threads::TRY_LOCK);
 
-       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) {
+       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, render_needed);
+       drawn_layout = _current_layout;
 
-       return true;
+       return dirty || render_needed;
 }
 
 bool
@@ -651,9 +617,9 @@ Push2::set_active (bool yn)
 
                init_buttons (true);
                init_touch_strip ();
-               set_pad_scale (scale_root, root_octave, mode, in_key);
-               switch_bank (0);
+               set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
                splash ();
+               set_current_layout (mix_layout);
 
        } else {
 
@@ -798,33 +764,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);
@@ -850,28 +816,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 */
@@ -955,16 +921,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
@@ -1114,10 +1070,10 @@ Push2::get_state()
        child->add_child_nocopy (_async_out->get_state());
        node.add_child_nocopy (*child);
 
-       node.add_property ("root", to_string (scale_root, std::dec));
-       node.add_property ("root_octave", to_string (root_octave, std::dec));
-       node.add_property ("in_key", in_key ? X_("yes") : X_("no"));
-       node.add_property ("mode", enum_2_string (mode));
+       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;
 }
@@ -1149,309 +1105,25 @@ 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;
-       }
+       XMLProperty const* prop;
 
-       boost::shared_ptr<SoloControl> ac = stripable[n]->solo_control ();
-       if (!ac) {
-               return;
+       if ((prop = node.property (X_("root"))) != 0) {
+               _scale_root = atoi (prop->value());
        }
 
-       Button* b = id_button_map[bid];
-
-       if (ac->soloed()) {
-               b->set_color (LED::Green);
-       } else {
-               b->set_color (LED::Black);
+       if ((prop = node.property (X_("root_octave"))) != 0) {
+               _root_octave = atoi (prop->value());
        }
 
-       if (ac->soloed_by_others_upstream() || ac->soloed_by_others_downstream()) {
-               b->set_state (LED::Blinking4th);
-       } else {
-               b->set_state (LED::OneShot24th);
+       if ((prop = node.property (X_("in_key"))) != 0) {
+               _in_key = string_is_affirmative (prop->value());
        }
 
-       write (b->state_msg());
-}
-
-void
-Push2::mute_change (int n)
-{
-       ButtonID bid;
-
-       if (!stripable[n]) {
-               return;
+       if ((prop = node.property (X_("mode"))) != 0) {
+               _mode = (MusicalMode::Type) string_2_enum (prop->value(), _mode);
        }
 
-       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 (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 (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 retval;
 }
 
 void
@@ -1500,7 +1172,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);
@@ -1510,9 +1182,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);
@@ -1521,31 +1193,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 ()
 {
@@ -1630,7 +1277,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);
@@ -1643,6 +1290,8 @@ Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
 
                                matched = true;
                        }
+               } else if ((*ev).is_pitch_bender() || (*ev).is_aftertouch() || (*ev).is_channel_pressure()) {
+                       out.push_back (*ev);
                }
        }
 
@@ -1712,16 +1361,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
 {
@@ -1737,8 +1376,6 @@ Push2::pad_note (int row, int col) const
 void
 Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
 {
-       cerr << "reset pad to r = " << root << " o = " << octave << " m = " << mode << endl;
-
        MusicalMode m (mode);
        vector<float>::iterator interval;
        int note;
@@ -1756,6 +1393,8 @@ Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
        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()) {
@@ -1866,8 +1505,233 @@ Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
 
        /* store state */
 
-       scale_root = root;
-       root_octave = octave;
-       in_key = inkey;
-       mode = mode;
+       _scale_root = original_root;
+       _root_octave = octave;
+       _in_key = inkey;
+       _mode = mode;
+}
+
+void
+Push2::set_percussive_mode (bool yn)
+{
+       if (!yn) {
+               cerr << "back to scale\n";
+               set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+               percussion = false;
+               return;
+       }
+
+       int drum_note = 36;
+
+       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++;
+               }
+       }
+
+       for (int row = 0; row < 8; ++row) {
+
+               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++;
+               }
+       }
+
+       percussion = true;
+
+       PadChange (); /* EMIT SIGNAL */
+}
+
+Push2Layout*
+Push2::current_layout () const
+{
+       Glib::Threads::Mutex::Lock lm (layout_lock);
+       return _current_layout;
+}
+
+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 ();
+       }
+}
+
+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::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)));
+
+}
+
+uint32_t
+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 (_current_layout) {
+               _current_layout->on_hide ();
+       }
+
+       _current_layout = layout;
+       drawn_layout = 0;
+
+       if (_current_layout) {
+               _current_layout->on_show ();
+       }
 }