convert codebase to use Temporal for various time types
[ardour.git] / libs / surfaces / push2 / mix.cc
index a4bebf463af9e8e28258cf4f2b8539eacabc6af4..22d4f330d7a243985840383357cbc3007e8cc505 100644 (file)
@@ -16,6 +16,7 @@
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
+#include <cairomm/region.h>
 #include <pangomm/layout.h>
 
 #include "pbd/compose.h"
@@ -27,8 +28,8 @@
 #include "pbd/enumwriter.h"
 
 #include "midi++/parser.h"
-#include "timecode/time.h"
-#include "timecode/bbt_time.h"
+#include "temporal/time.h"
+#include "temporal/bbt_time.h"
 
 #include "ardour/async_midi_port.h"
 #include "ardour/audioengine.h"
 #include "ardour/midi_port.h"
 #include "ardour/session.h"
 #include "ardour/tempo.h"
+#include "ardour/utils.h"
+#include "ardour/vca_manager.h"
 
+#include "gtkmm2ext/colors.h"
+#include "canvas/line.h"
+#include "canvas/rectangle.h"
+#include "canvas/text.h"
+
+#include "gtkmm2ext/gui_thread.h"
+
+#include "canvas.h"
+#include "knob.h"
+#include "level_meter.h"
+#include "mix.h"
 #include "push2.h"
+#include "utils.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
+
+#ifdef __APPLE__
+#define Rect ArdourCanvas::Rect
+#endif
 
 using namespace ARDOUR;
 using namespace std;
 using namespace PBD;
 using namespace Glib;
 using namespace ArdourSurface;
+using namespace Gtkmm2ext;
+using namespace ArdourCanvas;
 
-MixLayout::MixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context)
-       : Push2Layout (p, s)
+MixLayout::MixLayout (Push2& p, Session & s, std::string const & name)
+       : Push2Layout (p, s, name)
        , bank_start (0)
+       , vpot_mode (Volume)
 {
-       tc_clock_layout = Pango::Layout::create (context);
-       bbt_clock_layout = Pango::Layout::create (context);
+       /* background */
+
+       bg = new ArdourCanvas::Rectangle (this);
+       bg->set (Rect (0, 0, display_width(), display_height()));
+       bg->set_fill_color (p2.get_color (Push2::DarkBackground));
 
-       Pango::FontDescription fd ("Sans Bold 24");
-       tc_clock_layout->set_font_description (fd);
-       bbt_clock_layout->set_font_description (fd);
+       /* upper line */
+
+       upper_line = new Line (this);
+       upper_line->set (Duple (0, 22.5), Duple (display_width(), 22.5));
+       upper_line->set_outline_color (p2.get_color (Push2::LightBackground));
 
        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);
+
+               /* background for text labels for knob function */
+
+               ArdourCanvas::Rectangle* r = new ArdourCanvas::Rectangle (this);
+               Coord x0 = 10 + (n*Push2Canvas::inter_button_spacing()) - 5;
+               r->set (Rect (x0, 2, x0 + Push2Canvas::inter_button_spacing(), 2 + 21));
+               upper_backgrounds.push_back (r);
+
+               r = new ArdourCanvas::Rectangle (this);
+               r->set (Rect (x0, 137, x0 + Push2Canvas::inter_button_spacing(), 137 + 21));
+               lower_backgrounds.push_back (r);
+
+               /* text labels for knob function*/
+
+               Text* t = new Text (this);
+               t->set_font_description (fd2);
+               t->set_color (p2.get_color (Push2::ParameterName));
+               t->set_position (Duple (10 + (n*Push2Canvas::inter_button_spacing()), 5));
+
+               string txt;
+               switch (n) {
+               case 0:
+                       txt = _("Volumes");
+                       break;
+               case 1:
+                       txt = _("Pans");
+                       break;
+               case 2:
+                       txt = _("Pan Widths");
+                       break;
+               case 3:
+                       txt = _("A Sends");
+                       break;
+               case 4:
+                       txt = _("B Sends");
+                       break;
+               case 5:
+                       txt = _("C Sends");
+                       break;
+               case 6:
+                       txt = _("D Sends");
+                       break;
+               case 7:
+                       txt = _("E Sends");
+                       break;
+               }
+               t->set (txt);
+               upper_text.push_back (t);
+
+               /* GainMeters */
+
+               gain_meter[n] = new GainMeter (this, p2);
+               gain_meter[n]->set_position (Duple (40 + (n * Push2Canvas::inter_button_spacing()), 95));
+
+               /* stripable names */
+
+               t = new Text (this);
+               t->set_font_description (fd2);
+               t->set_color (p2.get_color (Push2::ParameterName));
+               t->set_position (Duple (10 + (n*Push2Canvas::inter_button_spacing()), 140));
+               lower_text.push_back (t);
+
        }
 
-       switch_bank (0);
+       mode_button = p2.button_by_id (Push2::Upper1);
+
+       session.RouteAdded.connect (session_connections, invalidator(*this), boost::bind (&MixLayout::stripables_added, this), &p2);
+       session.vca_manager().VCAAdded.connect (session_connections, invalidator (*this), boost::bind (&MixLayout::stripables_added, this), &p2);
 }
 
 MixLayout::~MixLayout ()
 {
+       // Item destructor deletes all children
 }
 
-bool
-MixLayout::redraw (Cairo::RefPtr<Cairo::Context> context) const
+void
+MixLayout::show ()
 {
-       framepos_t audible = session.audible_frame();
-       Timecode::Time TC;
-       bool negative = false;
-       string tc_clock_text;
-       string bbt_clock_text;
-
-       if (audible < 0) {
-               audible = -audible;
-               negative = true;
-       }
+       Push2::ButtonID upper_buttons[] = { Push2::Upper1, Push2::Upper2, Push2::Upper3, Push2::Upper4,
+                                           Push2::Upper5, Push2::Upper6, Push2::Upper7, Push2::Upper8 };
 
-       session.timecode_time (audible, TC);
 
-       TC.negative = TC.negative || negative;
+       for (size_t n = 0; n < sizeof (upper_buttons) / sizeof (upper_buttons[0]); ++n) {
+               Push2::Button* b = p2.button_by_id (upper_buttons[n]);
 
-       tc_clock_text = Timecode::timecode_format_time(TC);
+               if (b != mode_button) {
+                       b->set_color (Push2::LED::DarkGray);
+               } else {
+                       b->set_color (Push2::LED::White);
+               }
+               b->set_state (Push2::LED::OneShot24th);
+               p2.write (b->state_msg());
+       }
 
-       Timecode::BBT_Time bbt = session.tempo_map().bbt_at_frame (audible);
-       char buf[16];
+       switch_bank (bank_start);
 
-#define BBT_BAR_CHAR "|"
+       Container::show ();
+}
 
-       if (negative) {
-               snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
-                         bbt.bars, bbt.beats, bbt.ticks);
-       } else {
-               snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
-                         bbt.bars, bbt.beats, bbt.ticks);
+void
+MixLayout::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const
+{
+       Container::render (area, context);
+}
+
+void
+MixLayout::button_upper (uint32_t n)
+{
+       Push2::Button* b;
+       switch (n) {
+       case 0:
+               vpot_mode = Volume;
+               b = p2.button_by_id (Push2::Upper1);
+               break;
+       case 1:
+               vpot_mode = PanAzimuth;
+               b = p2.button_by_id (Push2::Upper2);
+               break;
+       case 2:
+               vpot_mode = PanWidth;
+               b = p2.button_by_id (Push2::Upper3);
+               break;
+       case 3:
+               vpot_mode = Send1;
+               b = p2.button_by_id (Push2::Upper4);
+               break;
+       case 4:
+               vpot_mode = Send2;
+               b = p2.button_by_id (Push2::Upper5);
+               break;
+       case 5:
+               vpot_mode = Send3;
+               b = p2.button_by_id (Push2::Upper6);
+               break;
+       case 6:
+               vpot_mode = Send4;
+               b = p2.button_by_id (Push2::Upper7);
+               break;
+       case 7:
+               vpot_mode = Send5;
+               b = p2.button_by_id (Push2::Upper8);
+               break;
        }
 
-       bbt_clock_text = buf;
+       if (b != mode_button) {
+               mode_button->set_color (Push2::LED::Black);
+               mode_button->set_state (Push2::LED::OneShot24th);
+               p2.write (mode_button->state_msg());
+       }
 
-       bool dirty = false;
+       mode_button = b;
 
-       if (tc_clock_text != tc_clock_layout->get_text()) {
-               dirty = true;
-               tc_clock_layout->set_text (tc_clock_text);
-       }
+       show_vpot_mode ();
+}
 
-       if (bbt_clock_text != tc_clock_layout->get_text()) {
-               dirty = true;
-               bbt_clock_layout->set_text (bbt_clock_text);
-       }
+void
+MixLayout::show_vpot_mode ()
+{
+       mode_button->set_color (Push2::LED::White);
+       mode_button->set_state (Push2::LED::OneShot24th);
+       p2.write (mode_button->state_msg());
 
-       string mid_text;
+       for (int s = 0; s < 8; ++s) {
+               upper_backgrounds[s]->hide ();
+               upper_text[s]->set_color (p2.get_color (Push2::ParameterName));
+       }
 
-       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;
+       uint32_t n = 0;
+
+       boost::shared_ptr<AutomationControl> ac;
+       switch (vpot_mode) {
+       case Volume:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->gain_control());
+                               boost::shared_ptr<PeakMeter> pm = stripable[s]->peak_meter(); 
+                               if (pm) {
+                                       gain_meter[s]->meter->set_meter (pm.get());
+                               } else {
+                                       gain_meter[s]->meter->set_meter (0);
+                               }
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
+                               gain_meter[s]->meter->set_meter (0);
                        }
+                       gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
+                       gain_meter[s]->meter->show ();
                }
-       }
+               n = 0;
+               break;
+       case PanAzimuth:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->pan_azimuth_control());
+                               gain_meter[s]->knob->add_flag (Push2Knob::ArcToZero);
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
 
-       if (!dirty) {
-               return false;
-       }
+                       }
+                       gain_meter[s]->meter->hide ();
+               }
+               n = 1;
+               break;
+       case PanWidth:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->pan_width_control());
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
 
-       context->set_source_rgb (0.764, 0.882, 0.882);
-       context->rectangle (0, 0, 960, 160);
-       context->fill ();
+                       }
+                       gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
+                       gain_meter[s]->meter->hide ();
+               }
+               n = 2;
+               break;
+       case Send1:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (0));
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
 
-       /* clocks */
+                       }
+                       gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
+                       gain_meter[s]->meter->hide ();
+               }
+               n = 3;
+               break;
+       case Send2:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (1));
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
 
-       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);
+                       }
+                       gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
+                       gain_meter[s]->meter->hide ();
+               }
+               n = 4;
+               break;
+       case Send3:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (2));
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
 
-       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);
-       }
+                       }
+                       gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
+                       gain_meter[s]->meter->hide ();
+               }
+               n = 5;
+               break;
+       case Send4:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (3));
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
 
-       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);
-       }
+                       }
+                       gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
+                       gain_meter[s]->meter->hide ();
+               }
+               n = 6;
+               break;
+       case Send5:
+               for (int s = 0; s < 8; ++s) {
+                       if (stripable[s]) {
+                               gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (4));
+                       } else {
+                               gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
 
-       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();
+                       }
+                       gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
+                       gain_meter[s]->meter->hide ();
                }
-               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);
+               n = 7;
+               break;
+       default:
+               break;
        }
 
-       return true;
+       upper_backgrounds[n]->set_fill_color (p2.get_color (Push2::ParameterName));
+       upper_backgrounds[n]->set_outline_color (p2.get_color (Push2::ParameterName));
+       upper_backgrounds[n]->show ();
+       upper_text[n]->set_color (contrasting_text_color (p2.get_color (Push2::ParameterName)));
 }
 
 void
-MixLayout::button_upper (uint32_t n)
+MixLayout::button_mute ()
 {
-       if (!stripable[n]) {
-               return;
+       boost::shared_ptr<Stripable> s = ControlProtocol::first_selected_stripable();
+       if (s) {
+               boost::shared_ptr<AutomationControl> ac = s->mute_control();
+               if (ac) {
+                       ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
+               }
        }
+}
 
-       if (p2.modifier_state() & Push2::ModShift) {
-               boost::shared_ptr<AutomationControl> sc = stripable[n]->rec_enable_control ();
-               if (sc) {
-                       sc->set_value (!sc->get_value(), PBD::Controllable::UseGroup);
-               }
-       } else {
-               boost::shared_ptr<SoloControl> sc = stripable[n]->solo_control ();
-               if (sc) {
-                       sc->set_value (!sc->self_soloed(), PBD::Controllable::UseGroup);
+void
+MixLayout::button_solo ()
+{
+       boost::shared_ptr<Stripable> s = ControlProtocol::first_selected_stripable();
+       if (s) {
+               boost::shared_ptr<AutomationControl> ac = s->solo_control();
+               if (ac) {
+                       session.set_control (ac, !ac->get_value(), PBD::Controllable::UseGroup);
                }
        }
 }
@@ -215,25 +413,18 @@ MixLayout::button_lower (uint32_t n)
                return;
        }
 
-       if (p2.modifier_state() & Push2::ModSelect) {
-               ControlProtocol::SetStripableSelection (stripable[n]);
-       } else {
-               boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
-
-               if (mc) {
-                       mc->set_value (!mc->muted_by_self(), PBD::Controllable::UseGroup);
-               }
-       }
+       ControlProtocol::SetStripableSelection (stripable[n]);
 }
 
 void
 MixLayout::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);
-               }
+       boost::shared_ptr<Controllable> ac = gain_meter[n]->knob->controllable();
+
+       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);
        }
 }
 
@@ -244,226 +435,211 @@ MixLayout::strip_vpot_touch (int n, bool touching)
                boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
                if (ac) {
                        if (touching) {
-                               ac->start_touch (session.audible_frame());
+                               ac->start_touch (session.audible_sample());
                        } else {
-                               ac->stop_touch (true, session.audible_frame());
+                               ac->stop_touch (session.audible_sample());
                        }
                }
        }
 }
 
 void
-MixLayout::stripable_property_change (PropertyChange const& what_changed, int which)
+MixLayout::stripable_property_change (PropertyChange const& what_changed, uint32_t which)
 {
+       if (what_changed.contains (Properties::color)) {
+               lower_backgrounds[which]->set_fill_color (stripable[which]->presentation_info().color());
+
+               if (stripable[which]->is_selected()) {
+                       lower_text[which]->set_fill_color (contrasting_text_color (stripable[which]->presentation_info().color()));
+                       /* might not be a MIDI track, in which case this will
+                          do nothing
+                       */
+                       p2.update_selection_color ();
+               }
+       }
+
+       if (what_changed.contains (Properties::hidden)) {
+               switch_bank (bank_start);
+       }
+
        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());
+               if (stripable[which]->is_selected()) {
+                       show_selection (which);
+               } else {
+                       hide_selection (which);
+               }
        }
-}
 
+}
 
 void
-MixLayout::solo_change (int n)
+MixLayout::show_selection (uint32_t n)
 {
-       Push2::ButtonID bid;
+       lower_backgrounds[n]->show ();
+       lower_backgrounds[n]->set_fill_color (stripable[n]->presentation_info().color());
+       lower_text[n]->set_color (contrasting_text_color (lower_backgrounds[n]->fill_color()));
+}
 
-       switch (n) {
-       case 0:
-               bid = Push2::Upper1;
-               break;
-       case 1:
-               bid = Push2::Upper2;
-               break;
-       case 2:
-               bid = Push2::Upper3;
-               break;
-       case 3:
-               bid = Push2::Upper4;
-               break;
-       case 4:
-               bid = Push2::Upper5;
-               break;
-       case 5:
-               bid = Push2::Upper6;
-               break;
-       case 6:
-               bid = Push2::Upper7;
-               break;
-       case 7:
-               bid = Push2::Upper8;
-               break;
-       default:
-               return;
+void
+MixLayout::hide_selection (uint32_t n)
+{
+       lower_backgrounds[n]->hide ();
+       if (stripable[n]) {
+               lower_text[n]->set_color (stripable[n]->presentation_info().color());
        }
+}
 
-       boost::shared_ptr<SoloControl> ac = stripable[n]->solo_control ();
-       if (!ac) {
-               return;
-       }
+void
+MixLayout::solo_changed (uint32_t n)
+{
+       solo_mute_changed (n);
+}
 
-       Push2::Button* b = p2.button_by_id (bid);
+void
+MixLayout::mute_changed (uint32_t n)
+{
+       solo_mute_changed (n);
+}
 
-       if (ac->soloed()) {
-               b->set_color (Push2::LED::Green);
-       } else {
-               b->set_color (Push2::LED::Black);
+void
+MixLayout::solo_mute_changed (uint32_t n)
+{
+       string shortname = short_version (stripable[n]->name(), 10);
+       string text;
+       boost::shared_ptr<AutomationControl> ac;
+       ac = stripable[n]->solo_control();
+       if (ac && ac->get_value()) {
+               text += "* ";
        }
-
-       if (ac->soloed_by_others_upstream() || ac->soloed_by_others_downstream()) {
-               b->set_state (Push2::LED::Blinking4th);
-       } else {
-               b->set_state (Push2::LED::OneShot24th);
+       boost::shared_ptr<MuteControl> mc;
+       mc = stripable[n]->mute_control ();
+       if (mc) {
+               if (mc->muted_by_self_or_masters()) {
+                       text += "! ";
+               } else if (mc->muted_by_others_soloing()) {
+                       text += "- "; // it would be nice to use Unicode mute"\uD83D\uDD07 ";
+               }
        }
-
-       p2.write (b->state_msg());
+       text += shortname;
+       lower_text[n]->set (text);
 }
 
 void
-MixLayout::mute_change (int n)
+MixLayout::switch_bank (uint32_t base)
 {
-       Push2::ButtonID bid;
+       stripable_connections.drop_connections ();
 
-       if (!stripable[n]) {
-               return;
-       }
+       /* work backwards so we can tell if we should actually switch banks */
 
-       cerr << "Mute changed on " << n << ' ' << stripable[n]->name() << endl;
+       boost::shared_ptr<Stripable> s[8];
+       uint32_t different = 0;
 
-       switch (n) {
-       case 0:
-               bid = Push2::Lower1;
-               break;
-       case 1:
-               bid = Push2::Lower2;
-               break;
-       case 2:
-               bid = Push2::Lower3;
-               break;
-       case 3:
-               bid = Push2::Lower4;
-               break;
-       case 4:
-               bid = Push2::Lower5;
-               break;
-       case 5:
-               bid = Push2::Lower6;
-               break;
-       case 6:
-               bid = Push2::Lower7;
-               break;
-       case 7:
-               bid = Push2::Lower8;
-               break;
-       default:
-               return;
+       for (int n = 0; n < 8; ++n) {
+               s[n] = session.get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
+               if (s[n] != stripable[n]) {
+                       different++;
+               }
        }
 
-       boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
-
-       if (!mc) {
+       if (!s[0]) {
+               /* not even the first stripable exists, do nothing */
+               for (int n = 0; n < 8; ++n) {
+                       stripable[n].reset ();
+                       gain_meter[n]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
+                       gain_meter[n]->meter->set_meter (0);
+               }
                return;
        }
 
-       Push2::Button* b = p2.button_by_id (bid);
+       for (int n = 0; n < 8; ++n) {
+               stripable[n] = s[n];
+       }
 
-       if (Config->get_show_solo_mutes() && !Config->get_solo_control_is_listen_control ()) {
+       /* at least one stripable in this bank */
 
-               if (mc->muted_by_self ()) {
-                       /* full mute */
-                       b->set_color (Push2::LED::Blue);
-                       b->set_state (Push2::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 (Push2::LED::Blue);
-                       b->set_state (Push2::LED::Blinking4th);
-                       cerr << "OTHER MUTE1\n";
-               } else {
-                       /* no mute at all */
-                       b->set_color (Push2::LED::Black);
-                       b->set_state (Push2::LED::OneShot24th);
-                       cerr << "NO MUTE1\n";
-               }
+       bank_start = base;
 
-       } else {
+       for (int n = 0; n < 8; ++n) {
 
-               if (mc->muted_by_self()) {
-                       /* full mute */
-                       b->set_color (Push2::LED::Blue);
-                       b->set_state (Push2::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 (Push2::LED::Blue);
-                       b->set_state (Push2::LED::Blinking4th);
-                       cerr << "OTHER MUTE1\n";
+               if (!stripable[n]) {
+                       lower_text[n]->hide ();
+                       hide_selection (n);
+                       gain_meter[n]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
+                       gain_meter[n]->meter->set_meter (0);
                } else {
-                       /* no mute at all */
-                       b->set_color (Push2::LED::Black);
-                       b->set_state (Push2::LED::OneShot24th);
-                       cerr << "NO MUTE2\n";
-               }
-       }
-
-       p2.write (b->state_msg());
-}
-
-void
-MixLayout::switch_bank (uint32_t base)
-{
-       stripable_connections.drop_connections ();
 
-       /* try to get the first stripable for the requested bank */
+                       lower_text[n]->show ();
 
-       stripable[0] = session.get_remote_nth_stripable (base, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
+                       /* stripable goes away? refill the bank, starting at the same point */
 
-       if (!stripable[0]) {
-               return;
-       }
+                       stripable[n]->DropReferences.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::switch_bank, this, bank_start), &p2);
+                       stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2);
+                       stripable[n]->solo_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::solo_changed, this, n), &p2);
+                       stripable[n]->mute_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::mute_changed, this, n), &p2);
 
-       /* at least one stripable in this bank */
-       bank_start = base;
+                       if (stripable[n]->is_selected()) {
+                               show_selection (n);
+                       } else {
+                               hide_selection (n);
+                       }
 
-       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));
+                       /* this will set lower text to the correct value (basically
+                          the stripable name)
+                       */
 
+                       solo_mute_changed (n);
 
-       for (int n = 0; n < 8; ++n) {
-               if (!stripable[n]) {
-                       continue;
+                       gain_meter[n]->knob->set_text_color (stripable[n]->presentation_info().color());
+                       gain_meter[n]->knob->set_arc_start_color (stripable[n]->presentation_info().color());
+                       gain_meter[n]->knob->set_arc_end_color (stripable[n]->presentation_info().color());
                }
 
-               /* stripable goes away? refill the bank, starting at the same point */
 
-               stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::switch_bank, this, bank_start), &p2);
-               boost::shared_ptr<AutomationControl> sc = stripable[n]->solo_control();
-               if (sc) {
-                       sc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::solo_change, this, n), &p2);
+               Push2::Button* b;
+
+               switch (n) {
+               case 0:
+                       b = p2.button_by_id (Push2::Lower1);
+                       break;
+               case 1:
+                       b = p2.button_by_id (Push2::Lower2);
+                       break;
+               case 2:
+                       b = p2.button_by_id (Push2::Lower3);
+                       break;
+               case 3:
+                       b = p2.button_by_id (Push2::Lower4);
+                       break;
+               case 4:
+                       b = p2.button_by_id (Push2::Lower5);
+                       break;
+               case 5:
+                       b = p2.button_by_id (Push2::Lower6);
+                       break;
+               case 6:
+                       b = p2.button_by_id (Push2::Lower7);
+                       break;
+               case 7:
+                       b = p2.button_by_id (Push2::Lower8);
+                       break;
                }
 
-               boost::shared_ptr<AutomationControl> mc = stripable[n]->mute_control();
-               if (mc) {
-                       mc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::mute_change, this, n), &p2);
+               if (stripable[n]) {
+                       b->set_color (p2.get_color_index (stripable[n]->presentation_info().color()));
+               } else {
+                       b->set_color (Push2::LED::Black);
                }
 
-               stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2);
-
-               solo_change (n);
-               mute_change (n);
-
+               b->set_state (Push2::LED::OneShot24th);
+               p2.write (b->state_msg());
        }
+
+       show_vpot_mode ();
 }
 
 void
@@ -478,6 +654,7 @@ MixLayout::button_left ()
        switch_bank (max (0, bank_start - 8));
 }
 
+
 void
 MixLayout::button_select_press ()
 {
@@ -495,7 +672,7 @@ MixLayout::button_select_release ()
 
        for (int n = 0; n < 8; ++n) {
                if (stripable[n]) {
-                       if (stripable[n]->presentation_info().selected()) {
+                       if (stripable[n]->is_selected()) {
                                        selected = n;
                                        break;
                        }
@@ -513,7 +690,6 @@ MixLayout::button_select_release ()
        } else {
 
                if (p2.modifier_state() & Push2::ModShift) {
-                       std::cerr << "select prev\n";
                        /* select prev */
 
                        if (selected == 0) {
@@ -540,7 +716,6 @@ MixLayout::button_select_release ()
 
                } else {
 
-                       std::cerr << "select next\n";
                        /* select next */
 
                        if (selected == 7) {
@@ -567,4 +742,44 @@ MixLayout::button_select_release ()
        }
 }
 
+void
+MixLayout::stripables_added ()
+{
+       /* reload current bank */
+       switch_bank (bank_start);
+}
+
+void
+MixLayout::button_down ()
+{
+       p2.scroll_dn_1_track ();
+}
 
+void
+MixLayout::button_up ()
+{
+       p2.scroll_up_1_track ();
+}
+
+void
+MixLayout::update_meters ()
+{
+       if (vpot_mode != Volume) {
+               return;
+       }
+
+       for (uint32_t n = 0; n < 8; ++n) {
+               gain_meter[n]->meter->update_meters ();
+       }
+}
+
+MixLayout::GainMeter::GainMeter (Item* parent, Push2& p2)
+       : Container (parent)
+{
+       knob = new Push2Knob (p2, this);
+       knob->set_radius (25);
+       /* leave position at (0,0) */
+
+       meter = new LevelMeter (p2, this, 90, ArdourCanvas::Meter::Vertical);
+       meter->set_position (Duple (40, -60));
+}