major design changes: use glib event loop for MIDI thread/UI; rework design of BaseUI...
[ardour.git] / libs / surfaces / mackie / mackie_control_protocol.cc
index 476391585417e70e5e70e006ca981cc1611fcaaa..6e65308f33597da4c94044dde356c832d48472f8 100644 (file)
@@ -14,8 +14,6 @@
        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.
-
-       $Id: mackie_control_protocol.cc 1059 2006-11-02 12:27:49Z paul $
 */
 
 #include <iostream>
@@ -23,6 +21,7 @@
 #include <cmath>
 #include <sstream>
 #include <vector>
+#include <iomanip>
 
 #define __STDC_FORMAT_MACROS
 #include <inttypes.h>
 
 #include <boost/shared_array.hpp>
 
-#include <midi++/types.h>
-#include <midi++/port.h>
-#include <midi++/manager.h>
-#include <pbd/pthread_utils.h>
-#include <pbd/error.h>
-
-#include <ardour/route.h>
-#include <ardour/session.h>
-#include <ardour/location.h>
-#include <ardour/dB.h>
-#include <ardour/panner.h>
+#include "midi++/types.h"
+#include "midi++/port.h"
+#include "midi++/manager.h"
+#include "pbd/pthread_utils.h"
+#include "pbd/error.h"
+#include "pbd/memento_command.h"
+#include "pbd/convert.h"
+
+#include "ardour/session.h"
+#include "ardour/route.h"
+#include "ardour/location.h"
+#include "ardour/dB.h"
+#include "ardour/panner.h"
+#include "ardour/tempo.h"
+#include "ardour/types.h"
 
 #include "mackie_control_protocol.h"
 
@@ -68,45 +71,45 @@ using boost::shared_ptr;
 
 MackieMidiBuilder builder;
 
-// Copied from tranzport_control_protocol.cc
-static inline double 
-gain_to_slider_position (ARDOUR::gain_t g)
-{
-       if (g == 0) return 0;
-       return pow((6.0*log(g)/log(2.0)+192.0)/198.0, 8.0);
-}
-
-/*
-       Copied from tranzport_control_protocol.cc
-       TODO this seems to return slightly wrong values, namely
-       with the UI slider at max, we get a 0.99something value.
-*/
-static inline ARDOUR::gain_t 
-slider_position_to_gain (double pos)
-{
-       /* XXX Marcus writes: this doesn't seem right to me. but i don't have a better answer ... */
-       if (pos == 0.0) return 0;
-       return pow (2.0,(sqrt(sqrt(sqrt(pos)))*198.0-192.0)/6.0);
-}
-
 MackieControlProtocol::MackieControlProtocol (Session& session)
        : ControlProtocol  (session, X_("Mackie"))
        , _current_initial_bank( 0 )
        , connections_back( _connections )
        , _surface( 0 )
        , _ports_changed( false )
+       , _polling( true )
        , pfd( 0 )
        , nfds( 0 )
+       , _jog_wheel( *this )
+       , _timecode_type( ARDOUR::AnyTime::BBT )
 {
+#ifdef DEBUG
        cout << "MackieControlProtocol::MackieControlProtocol" << endl;
+#endif
        // will start reading from ports, as soon as there are some
-       pthread_create_and_store (X_("mackie monitor"), &thread, 0, _monitor_work, this);
+       pthread_create_and_store (X_("mackie monitor"), &thread, _monitor_work, this);
 }
 
 MackieControlProtocol::~MackieControlProtocol()
 {
+#ifdef DEBUG
        cout << "~MackieControlProtocol::MackieControlProtocol" << endl;
-       close();
+#endif
+       try
+       {
+               close();
+       }
+       catch ( exception & e )
+       {
+               cout << "~MackieControlProtocol caught " << e.what() << endl;
+       }
+       catch ( ... )
+       {
+               cout << "~MackieControlProtocol caught unknown" << endl;
+       }
+#ifdef DEBUG
+       cout << "finished ~MackieControlProtocol::MackieControlProtocol" << endl;
+#endif
 }
 
 Mackie::Surface & MackieControlProtocol::surface()
@@ -118,14 +121,28 @@ Mackie::Surface & MackieControlProtocol::surface()
        return *_surface;
 }
 
-const Mackie::MackiePort & MackieControlProtocol::mcu_port() const
+const Mackie::SurfacePort & MackieControlProtocol::mcu_port() const
 {
-       return dynamic_cast<const MackiePort &>( *_ports[0] );
+       if ( _ports.size() < 1 )
+       {
+               return _dummy_port;
+       }
+       else
+       {
+               return dynamic_cast<const MackiePort &>( *_ports[0] );
+       }
 }
 
-Mackie::MackiePort & MackieControlProtocol::mcu_port()
+Mackie::SurfacePort & MackieControlProtocol::mcu_port()
 {
-       return dynamic_cast<const MackiePort &>( *_ports[0] );
+       if ( _ports.size() < 1 )
+       {
+               return _dummy_port;
+       }
+       else
+       {
+               return dynamic_cast<MackiePort &>( *_ports[0] );
+       }
 }
 
 // go to the previous track.
@@ -170,7 +187,7 @@ MackiePort & MackieControlProtocol::port_for_id( uint32_t index )
                current_max += (*it)->strips();
                if ( index < current_max ) return **it;
        }
-       
+
        // oops - no matching port
        ostringstream os;
        os << "No port for index " << index;
@@ -199,25 +216,25 @@ struct RouteByRemoteId
 MackieControlProtocol::Sorted MackieControlProtocol::get_sorted_routes()
 {
        Sorted sorted;
-       
+
        // fetch all routes
-       boost::shared_ptr<Session::RouteList> routes = session->get_routes();
+       boost::shared_ptr<RouteList> routes = session->get_routes();
        set<uint32_t> remote_ids;
-       
+
        // routes with remote_id 0 should never be added
        // TODO verify this with ardour devs
        // remote_ids.insert( 0 );
-       
+
        // sort in remote_id order, and exclude master, control and hidden routes
        // and any routes that are already set.
-       for ( Session::RouteList::iterator it = routes->begin(); it != routes->end(); ++it )
+       for (RouteList::iterator it = routes->begin(); it != routes->end(); ++it )
        {
                Route & route = **it;
                if (
                                route.active()
-                               && !route.master()
-                               && !route.hidden()
-                               && !route.control()
+                               && !route.is_master()
+                               && !route.is_hidden()
+                               && !route.is_control()
                                && remote_ids.find( route.remote_control_id() ) == remote_ids.end()
                )
                {
@@ -238,21 +255,23 @@ void MackieControlProtocol::switch_banks( int initial )
 {
        // DON'T prevent bank switch if initial == _current_initial_bank
        // because then this method can't be used as a refresh
-       
+
        // sanity checking
        Sorted sorted = get_sorted_routes();
        int delta = sorted.size() - route_table.size();
        if ( initial < 0 || ( delta > 0 && initial > delta ) )
        {
+#ifdef DEBUG
                cout << "not switching to " << initial << endl;
+#endif
                return;
        }
        _current_initial_bank = initial;
-       
+
        // first clear the signals from old routes
        // taken care of by the RouteSignal destructors
        clear_route_signals();
-       
+
        // now set the signals for new routes
        if ( _current_initial_bank <= sorted.size() )
        {
@@ -260,74 +279,55 @@ void MackieControlProtocol::switch_banks( int initial )
                uint32_t end_pos = min( route_table.size(), sorted.size() );
                Sorted::iterator it = sorted.begin() + _current_initial_bank;
                Sorted::iterator end = sorted.begin() + _current_initial_bank + end_pos;
-               //cout << "switch to " << _current_initial_bank << ", " << end_pos << endl;
-               
+#ifdef DEBUG
+               cout << "switch to " << _current_initial_bank << ", " << end_pos << endl;
+#endif
+
                // link routes to strips
                uint32_t i = 0;
                for ( ; it != end && it != sorted.end(); ++it, ++i )
                {
                        boost::shared_ptr<Route> route = *it;
                        Strip & strip = *surface().strips[i];
-                       //cout << "remote id " << route->remote_control_id() << " connecting " << route->name() << " to " << strip.name() << " with port " << port_for_id(i) << endl;
+#ifdef DEBUG
+                       cout << "remote id " << route->remote_control_id() << " connecting " << route->name() << " to " << strip.name() << " with port " << port_for_id(i) << endl;
+#endif
                        route_table[i] = route;
-                       RouteSignal * rs = new RouteSignal( *route, *this, strip, port_for_id(i) );
+                       RouteSignal * rs = new RouteSignal (route, *this, strip, port_for_id(i) );
                        route_signals.push_back( rs );
                        // update strip from route
                        rs->notify_all();
                }
-               
+
                // create dead strips if there aren't enough routes to
                // fill a bank
                for ( ; i < route_table.size(); ++i )
                {
                        Strip & strip = *surface().strips[i];
                        // send zero for this strip
-                       port_for_id(i).write( builder.zero_strip( strip ) );
+                       MackiePort & port = port_for_id(i);
+                       port.write( builder.zero_strip( port, strip ) );
                }
        }
-       
+
        // display the current start bank.
-       if ( mcu_port().emulation() == MackiePort::bcf2000 )
-       {
-               if ( _current_initial_bank == 0 )
-               {
-                       // send Ar. to 2-char display on the master
-                       mcu_port().write( builder.two_char_display( "Ar", ".." ) );
-               }
-               else
-               {
-                       // write the current first remote_id to the 2-char display
-                       mcu_port().write( builder.two_char_display( _current_initial_bank ) );
-               }
-       }
+       surface().display_bank_start( mcu_port(), builder, _current_initial_bank );
 }
 
 void MackieControlProtocol::zero_all()
 {
-       // TODO turn off 55-char and SMPTE displays
-       
-       if ( mcu_port().emulation() == MackiePort::bcf2000 )
-       {
-               // clear 2-char display
-               mcu_port().write( builder.two_char_display( "LC" ) );
-       }
-       
+       // TODO turn off Timecode displays
+
        // zero all strips
        for ( Surface::Strips::iterator it = surface().strips.begin(); it != surface().strips.end(); ++it )
        {
-               port_for_id( (*it)->index() ).write( builder.zero_strip( **it ) );
+               MackiePort & port = port_for_id( (*it)->index() );
+               port.write( builder.zero_strip( port, **it ) );
        }
-       
+
        // and the master strip
-       mcu_port().write( builder.zero_strip( master_strip() ) );
-       
-       // and the led ring for the master strip, in bcf mode
-       if ( mcu_port().emulation() == MackiePort::bcf2000 )
-       {
-               Control & control = *surface().controls_by_name["jog"];
-               mcu_port().write( builder.build_led_ring( dynamic_cast<Pot &>( control ), off ) );
-       }
-       
+       mcu_port().write( builder.zero_strip( dynamic_cast<MackiePort&>( mcu_port() ), master_strip() ) );
+
        // turn off global buttons and leds
        // global buttons are only ever on mcu_port, so we don't have
        // to figure out which port.
@@ -339,9 +339,12 @@ void MackieControlProtocol::zero_all()
                        mcu_port().write( builder.zero_control( control ) );
                }
        }
+
+       // any hardware-specific stuff
+       surface().zero_all( mcu_port(), builder );
 }
 
-int MackieControlProtocol::set_active (bool yn)
+int MackieControlProtocol::set_active( bool yn )
 {
        if ( yn != _active )
        {
@@ -352,58 +355,61 @@ int MackieControlProtocol::set_active (bool yn)
                        if ( yn )
                        {
                                // TODO what happens if this fails half way?
-                               cout << "set_active true" << endl;
-                               
+
                                // create MackiePorts
                                {
                                        Glib::Mutex::Lock lock( update_mutex );
                                        create_ports();
                                }
-                               
+
+                               // make sure the ports are being listened to
+                               update_ports();
+
                                // wait until poll thread is running, with ports to poll
                                // the mutex is only there because conditions require a mutex
                                {
                                        Glib::Mutex::Lock lock( update_mutex );
                                        while ( nfds == 0 ) update_cond.wait( update_mutex );
                                }
-                               
+
                                // now initialise MackiePorts - ie exchange sysex messages
                                for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it )
                                {
                                        (*it)->open();
                                }
-                               
+
                                // wait until all ports are active
-                               // TODO a more sophisticate approach would
+                               // TODO a more sophisticated approach would
                                // allow things to start up with only an MCU, even if
                                // extenders were specified but not responding.
                                for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it )
                                {
                                        (*it)->wait_for_init();
                                }
-                               
+
                                // create surface object. This depends on the ports being
                                // correctly initialised
                                initialize_surface();
                                connect_session_signals();
-                               
+
                                // yeehah!
                                _active = true;
-                               
+
                                // send current control positions to surface
                                // must come after _active = true otherwise it won't run
                                update_surface();
                        }
                        else
                        {
-                               cout << "set_active false" << endl;
                                close();
                                _active = false;
                        }
                }
                catch( exception & e )
                {
-                       cout << "set_active to false because " << e.what() << endl;
+#ifdef DEBUG
+                       cout << "set_active to false because exception caught: " << e.what() << endl;
+#endif
                        _active = false;
                        throw;
                }
@@ -444,47 +450,86 @@ bool MackieControlProtocol::handle_strip_button( Control & control, ButtonState
                        //state = default_button_press( dynamic_cast<Button&>( control ) );
                }
        }
-       
+
        if ( control.name() == "fader_touch" )
        {
                state = bs == press;
-               control.strip().gain().touch( state );
+               control.strip().gain().in_use( state );
        }
-       
+
        return state;
 }
 
 void MackieControlProtocol::update_led( Mackie::Button & button, Mackie::LedState ls )
 {
-       MackiePort * port = 0;
-       if ( button.group().is_strip() )
+       if ( ls != none )
        {
-               if ( button.group().is_master() )
+               SurfacePort * port = 0;
+               if ( button.group().is_strip() )
                {
-                       port = &mcu_port();
+                       if ( button.group().is_master() )
+                       {
+                               port = &mcu_port();
+                       }
+                       else
+                       {
+                               port = &port_for_id( dynamic_cast<const Strip&>( button.group() ).index() );
+                       }
                }
                else
                {
-                       port = &port_for_id( dynamic_cast<const Strip&>( button.group() ).index() );
+                       port = &mcu_port();
                }
+               port->write( builder.build_led( button, ls ) );
        }
-       else
+}
+
+void MackieControlProtocol::update_timecode_beats_led()
+{
+       switch ( _timecode_type )
        {
-               port = &mcu_port();
+               case ARDOUR::AnyTime::BBT:
+                       update_global_led( "beats", on );
+                       update_global_led( "timecode", off );
+                       break;
+               case ARDOUR::AnyTime::Timecode:
+                       update_global_led( "timecode", on );
+                       update_global_led( "beats", off );
+                       break;
+               default:
+                       ostringstream os;
+                       os << "Unknown Anytime::Type " << _timecode_type;
+                       throw runtime_error( os.str() );
        }
-       if ( ls != none ) port->write( builder.build_led( button, ls ) );
 }
 
 void MackieControlProtocol::update_global_button( const string & name, LedState ls )
 {
-       if ( surface().controls_by_name.find( name ) !=surface().controls_by_name.end() )
+       if ( surface().controls_by_name.find( name ) != surface().controls_by_name.end() )
        {
                Button * button = dynamic_cast<Button*>( surface().controls_by_name[name] );
                mcu_port().write( builder.build_led( button->led(), ls ) );
        }
        else
        {
+#ifdef DEBUG
                cout << "Button " << name << " not found" << endl;
+#endif
+       }
+}
+
+void MackieControlProtocol::update_global_led( const string & name, LedState ls )
+{
+       if ( surface().controls_by_name.find( name ) != surface().controls_by_name.end() )
+       {
+               Led * led = dynamic_cast<Led*>( surface().controls_by_name[name] );
+               mcu_port().write( builder.build_led( *led, ls ) );
+       }
+       else
+       {
+#ifdef DEBUG
+               cout << "Led " << name << " not found" << endl;
+#endif
        }
 }
 
@@ -496,16 +541,23 @@ void MackieControlProtocol::update_surface()
                // do the initial bank switch to connect signals
                // _current_initial_bank is initialised by set_state
                switch_banks( _current_initial_bank );
-               
+
                // create a RouteSignal for the master route
-               // but only the first time around
-               master_route_signal = shared_ptr<RouteSignal>( new RouteSignal( *master_route(), *this, master_strip(), mcu_port() ) );
-               // update strip from route
-               master_route_signal->notify_all();
-               
+
+               boost::shared_ptr<Route> mr = master_route ();
+               if (mr) {
+                       master_route_signal = shared_ptr<RouteSignal> (new RouteSignal (mr, *this, master_strip(), mcu_port()) );
+                       // update strip from route
+                       master_route_signal->notify_all();
+               }
+
+               // sometimes the jog wheel is a pot
+               surface().blank_jog_ring( mcu_port(), builder );
+
                // update global buttons and displays
                notify_record_state_changed();
                notify_transport_state_changed();
+               update_timecode_beats_led();
        }
 }
 
@@ -519,47 +571,62 @@ void MackieControlProtocol::connect_session_signals()
        connections_back = session->TransportStateChange.connect( ( mem_fun (*this, &MackieControlProtocol::notify_transport_state_changed) ) );
        // receive punch-in and punch-out
        connections_back = Config->ParameterChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_parameter_changed) ) );
+       session->config.ParameterChanged.connect ( ( mem_fun (*this, &MackieControlProtocol::notify_parameter_changed) ) );
        // receive rude solo changed
        connections_back = session->SoloActive.connect( ( mem_fun (*this, &MackieControlProtocol::notify_solo_active_changed) ) );
-       
+
        // make sure remote id changed signals reach here
        // see also notify_route_added
        Sorted sorted = get_sorted_routes();
        for ( Sorted::iterator it = sorted.begin(); it != sorted.end(); ++it )
        {
-               (*it)->RemoteControlIDChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_remote_id_changed) ) );
+               connections_back = (*it)->RemoteControlIDChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_remote_id_changed) ) );
        }
 }
 
 void MackieControlProtocol::add_port( MIDI::Port & midi_port, int number )
 {
-       MackiePort * sport = new MackiePort( *this, midi_port, number );
-       _ports.push_back( sport );
+#ifdef DEBUG
+       cout << "add port " << midi_port.name() << ", " << midi_port.device() << ", " << midi_port.type() << endl;
+       cout << "MIDI::Port::ALSA_Sequencer " << MIDI::Port::ALSA_Sequencer << endl;
+       cout << "MIDI::Port::Unknown " << MIDI::Port::Unknown << endl;
+#endif
+       if ( string( midi_port.device() ) == string( "ardour" ) && midi_port.type() == MIDI::Port::ALSA_Sequencer )
+       {
+               throw MackieControlException( "The Mackie MCU driver will not use a port with device=ardour" );
+       }
+       else if ( midi_port.type() == MIDI::Port::ALSA_Sequencer )
+       {
+               throw MackieControlException( "alsa/sequencer ports don't work with the Mackie MCU driver right now" );
+       }
+       else
+       {
+               MackiePort * sport = new MackiePort( *this, midi_port, number );
+               _ports.push_back( sport );
 
-       connections_back = sport->init_event.connect(
-               sigc::bind (
-                       mem_fun (*this, &MackieControlProtocol::handle_port_init)
-                       , sport
-               )
-       );
+               connections_back = sport->init_event.connect(
+                       sigc::bind (
+                               mem_fun (*this, &MackieControlProtocol::handle_port_init)
+                               , sport
+                       )
+               );
 
-       connections_back = sport->active_event.connect(
-               sigc::bind (
-                       mem_fun (*this, &MackieControlProtocol::handle_port_changed)
-                       , sport
-                       , true
-               )
-       );
+               connections_back = sport->active_event.connect(
+                       sigc::bind (
+                               mem_fun (*this, &MackieControlProtocol::handle_port_active)
+                               , sport
+                       )
+               );
 
-       connections_back = sport->inactive_event.connect(
-               sigc::bind (
-                       mem_fun (*this, &MackieControlProtocol::handle_port_changed)
-                       , sport
-                       , false
-               )
-       );
-       
-       _ports_changed = true;
+               connections_back = sport->inactive_event.connect(
+                       sigc::bind (
+                               mem_fun (*this, &MackieControlProtocol::handle_port_inactive)
+                               , sport
+                       )
+               );
+
+               _ports_changed = true;
+       }
 }
 
 void MackieControlProtocol::create_ports()
@@ -578,7 +645,7 @@ void MackieControlProtocol::create_ports()
                }
                add_port( *midi_port, 0 );
        }
-       
+
        // open extender ports. Up to 9. Should be enough.
        // could also use mm->get_midi_ports()
        string ext_port_base = "mcu_xt_";
@@ -593,13 +660,7 @@ void MackieControlProtocol::create_ports()
 
 shared_ptr<Route> MackieControlProtocol::master_route()
 {
-       shared_ptr<Route> retval;
-       retval = session->route_by_name( "master" );
-       if ( retval == 0 )
-       {
-               // TODO search through all routes for one with the master attribute set
-       }
-       return retval;
+       return session->master_out ();
 }
 
 Strip & MackieControlProtocol::master_strip()
@@ -615,32 +676,56 @@ void MackieControlProtocol::initialize_surface()
        {
                strips += (*it)->strips();
        }
-       
+
        set_route_table_size( strips );
-       
-       switch ( mcu_port().emulation() )
+
+       // TODO same as code in mackie_port.cc
+       string emulation = ARDOUR::Config->get_mackie_emulation();
+       if ( emulation == "bcf" )
        {
-               case MackiePort::bcf2000: _surface = new BcfSurface( strips ); break;
-               case MackiePort::mackie: _surface = new MackieSurface( strips ); break;
-               default:
-                       ostringstream os;
-                       os << "no Surface class found for emulation: " << mcu_port().emulation();
-                       throw MackieControlException( os.str() );
+               _surface = new BcfSurface( strips );
        }
+       else if ( emulation == "mcu" )
+       {
+               _surface = new MackieSurface( strips );
+       }
+       else
+       {
+               ostringstream os;
+               os << "no Surface class found for emulation: " << emulation;
+               throw MackieControlException( os.str() );
+       }
+
        _surface->init();
-       
+
        // Connect events. Must be after route table otherwise there will be trouble
        for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it )
        {
-               (*it)->control_event.connect( ( mem_fun (*this, &MackieControlProtocol::handle_control_event) ) );
+               connections_back = (*it)->control_event.connect( ( mem_fun (*this, &MackieControlProtocol::handle_control_event) ) );
        }
 }
 
 void MackieControlProtocol::close()
 {
+       // stop polling, and wait for it...
+       // must be before other shutdown otherwise polling loop
+       // calls methods on objects that are deleted
+       _polling = false;
+       pthread_join( thread, 0 );
+
        // TODO disconnect port active/inactive signals
        // Or at least put a lock here
-       
+
+       // disconnect global signals from Session
+       // TODO Since *this is a sigc::trackable, this shouldn't be necessary
+       // but it is for some reason
+#if 0
+       for( vector<sigc::connection>::iterator it = _connections.begin(); it != _connections.end(); ++it )
+       {
+               it->disconnect();
+       }
+#endif
+
        if ( _surface != 0 )
        {
                // These will fail if the port has gone away.
@@ -653,9 +738,11 @@ void MackieControlProtocol::close()
                }
                catch ( exception & e )
                {
+#ifdef DEBUG
                        cout << "MackieControlProtocol::close caught exception: " << e.what() << endl;
+#endif
                }
-               
+
                for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it )
                {
                        try
@@ -670,33 +757,30 @@ void MackieControlProtocol::close()
                        }
                        catch ( exception & e )
                        {
+#ifdef DEBUG
                                cout << "MackieControlProtocol::close caught exception: " << e.what() << endl;
+#endif
                        }
                }
-               
-               // disconnect global signals from Session
-               // TODO Since *this is a sigc::trackable, this shouldn't be necessary
-               for( vector<sigc::connection>::iterator it = _connections.begin(); it != _connections.end(); ++it )
-               {
-                       it->disconnect();
-               }
-               
+
                // disconnect routes from strips
                clear_route_signals();
+
+               delete _surface;
+               _surface = 0;
        }
-       
-       // stop polling, and wait for it...
-       pthread_cancel_one( thread );
-       pthread_join( thread, 0 );
-       
+
        // shut down MackiePorts
        for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it )
        {
                delete *it;
        }
        _ports.clear();
-       
+
+       // this is done already in monitor_work. But it's here so we know.
        delete[] pfd;
+       pfd = 0;
+       nfds = 0;
 }
 
 void* MackieControlProtocol::_monitor_work (void* arg)
@@ -706,25 +790,29 @@ void* MackieControlProtocol::_monitor_work (void* arg)
 
 XMLNode & MackieControlProtocol::get_state()
 {
+#ifdef DEBUG
        cout << "MackieControlProtocol::get_state" << endl;
-       
+#endif
+
        // add name of protocol
        XMLNode* node = new XMLNode( X_("Protocol") );
        node->add_property( X_("name"), _name );
-       
+
        // add current bank
        ostringstream os;
        os << _current_initial_bank;
        node->add_property( X_("bank"), os.str() );
-       
+
        return *node;
 }
 
-int MackieControlProtocol::set_state( const XMLNode & node )
+int MackieControlProtocol::set_state (const XMLNode & node, int /*version*/)
 {
+#ifdef DEBUG
        cout << "MackieControlProtocol::set_state: active " << _active << endl;
+#endif
        int retval = 0;
-       
+
        // fetch current bank
        if ( node.property( X_("bank") ) != 0 )
        {
@@ -737,17 +825,19 @@ int MackieControlProtocol::set_state( const XMLNode & node )
                }
                catch ( exception & e )
                {
+#ifdef DEBUG
                        cout << "exception in MackieControlProtocol::set_state: " << e.what() << endl;
+#endif
                        return -1;
                }
        }
-       
+
        return retval;
 }
 
 void MackieControlProtocol::handle_control_event( SurfacePort & port, Control & control, const ControlState & state )
 {
-       uint32_t index = control.ordinal() - 1 + ( port.number() * port.strips() );
+       // find the route for the control, if there is one
        boost::shared_ptr<Route> route;
        if ( control.group().is_strip() )
        {
@@ -755,45 +845,36 @@ void MackieControlProtocol::handle_control_event( SurfacePort & port, Control &
                {
                        route = master_route();
                }
-               else if ( index < route_table.size() )
-                       route = route_table[index];
                else
-                       cerr << "Warning: index is " << index << " which is not in the route table, size: " << route_table.size() << endl;
+               {
+                       uint32_t index = control.ordinal() - 1 + ( port.number() * port.strips() );
+                       if ( index < route_table.size() )
+                               route = route_table[index];
+                       else
+                               cerr << "Warning: index is " << index << " which is not in the route table, size: " << route_table.size() << endl;
+               }
        }
-       
+
        // This handles control element events from the surface
        // the state of the controls on the surface is usually updated
        // from UI events.
        switch ( control.type() )
        {
                case Control::type_fader:
-                       if ( control.group().is_strip() )
+                       // find the route in the route table for the id
+                       // if the route isn't available, skip it
+                       // at which point the fader should just reset itself
+                       if ( route != 0 )
                        {
-                               // find the route in the route table for the id
-                               // if the route isn't available, skip it
-                               // at which point the fader should just reset itself
-                               if ( route != 0 )
-                               {
-                                       route->set_gain( slider_position_to_gain( state.pos ), this );
-                                       
-                                       // must echo bytes back to slider now, because
-                                       // the notifier only works if the fader is not being
-                                       // touched. Which it is if we're getting input.
-                                       port.write( builder.build_fader( (Fader&)control, state.pos ) );
-                               }
-                       }
-                       else
-                       {
-                               // master fader
-                               boost::shared_ptr<Route> route = master_route();
-                               if ( route )
-                               {
-                                       route->set_gain( slider_position_to_gain( state.pos ), this );
-                                       port.write( builder.build_fader( (Fader&)control, state.pos ) );
-                               }
+                               route->gain_control()->set_value( state.pos );
+
+                               // must echo bytes back to slider now, because
+                               // the notifier only works if the fader is not being
+                               // touched. Which it is if we're getting input.
+                               port.write( builder.build_fader( (Fader&)control, state.pos ) );
                        }
                        break;
-                       
+
                case Control::type_button:
                        if ( control.group().is_strip() )
                        {
@@ -812,9 +893,10 @@ void MackieControlProtocol::handle_control_event( SurfacePort & port, Control &
                        else if ( control.group().is_master() )
                        {
                                // master fader touch
-                               boost::shared_ptr<Route> route = master_route();
-                               if ( route )
+                               if ( route != 0 )
+                               {
                                        handle_strip_button( control, state.button_state, route );
+                               }
                        }
                        else
                        {
@@ -822,27 +904,28 @@ void MackieControlProtocol::handle_control_event( SurfacePort & port, Control &
                                surface().handle_button( *this, state.button_state, dynamic_cast<Button&>( control ) );
                        }
                        break;
-                       
+
                // pot (jog wheel, external control)
                case Control::type_pot:
                        if ( control.group().is_strip() )
                        {
-                               if ( route != 0 )
+                               if ( route != 0 && route->panner() )
                                {
-                                       if ( route->panner().size() == 1 )
+                                       // pan for mono input routes, or stereo linked panners
+                                       if ( route->panner()->npanners() == 1 || ( route->panner()->npanners() == 2 && route->panner()->linked() ) )
                                        {
                                                // assume pan for now
                                                float xpos;
-                                               route->panner()[0]->get_effective_position (xpos);
-                                               
+                                               route->panner()->streampanner (0).get_effective_position (xpos);
+
                                                // calculate new value, and trim
-                                               xpos += state.delta;
+                                               xpos += state.delta * state.sign;
                                                if ( xpos > 1.0 )
                                                        xpos = 1.0;
                                                else if ( xpos < 0.0 )
                                                        xpos = 0.0;
-                                               
-                                               route->panner()[0]->set_position( xpos );
+
+                                               route->panner()->streampanner (0).set_position( xpos );
                                        }
                                }
                                else
@@ -853,100 +936,48 @@ void MackieControlProtocol::handle_control_event( SurfacePort & port, Control &
                        }
                        else
                        {
-                               if ( control.name() == "jog" )
+                               if ( control.is_jog() )
                                {
-                                       // TODO use current snap-to setting?
-                                       long delta = state.ticks * 1000;
-                                       nframes_t next = session->transport_frame() + delta;
-                                       if ( delta < 0 && session->transport_frame() < (nframes_t) abs( delta )  )
-                                       {
-                                               next = session->current_start_frame();
-                                       }
-                                       else if ( next > session->current_end_frame() )
-                                       {
-                                               next = session->current_end_frame();
-                                       }
-                                       
-                                       // moves jerkily and doesn't really work. eventually core-dumps
-                                       session->request_locate( next, session->transport_rolling() );
-                                       // ditto
-                                       // ScrollTimeline( state.ticks );
-                                       // mtaht says maybe snap-to code has some ideas
-                                       
-                                       // turn off the led ring, for bcf emulation mode
-                                       port.write( builder.build_led_ring( dynamic_cast<Pot &>( control ), off ) );
+                                       _jog_wheel.jog_event( port, control, state );
                                }
                                else
                                {
-                                       cout << "external controller" << state.ticks << endl;
+                                       cout << "external controller" << state.ticks * state.sign << endl;
                                }
                        }
                        break;
-                       
+
                default:
                        cout << "Control::type not handled: " << control.type() << endl;
        }
 }
 
-/////////////////////////////////////////////////////////
-// Notifications from UI
-/////////////////////////////////////////////////////////
-struct RouteSignalByRoute
-{
-       RouteSignalByRoute( Route & route ): _route( route ) {}
-               
-       bool operator () ( const RouteSignal & rs ) const
-       {
-               return rs.route().id() == _route.id();
-       }
-       
-       bool operator () ( const RouteSignal * rs ) const
-       {
-               return rs->route().id() == _route.id();
-       }
-
-       Route & _route;
-};
+/////////////////////////////////////////////////
+// handlers for Route signals
+// TODO should these be part of RouteSignal?
+// They started off as sigc handlers for signals
+// from Route, but they're also used in polling for automation
+/////////////////////////////////////////////////
 
-Strip & MackieControlProtocol::strip_from_route( Route * route )
+void MackieControlProtocol::notify_solo_changed( RouteSignal * route_signal )
 {
-       if ( route == 0 )
-       {
-               throw MackieControlException( "route is null in MackieControlProtocol::strip_from_route" );
-       }
-       
-       if ( route->master() )
+       try
        {
-               return master_strip();
+               Button & button = route_signal->strip().solo();
+               route_signal->port().write( builder.build_led( button, route_signal->route()->soloed() ) );
        }
-       
-       RouteSignals::iterator it = find_if(
-               route_signals.begin()
-               , route_signals.end()
-               , RouteSignalByRoute( *route )
-       );
-       
-       if ( it == route_signals.end() )
+       catch( exception & e )
        {
-               ostringstream os;
-               os << "No strip for route " << route->name();
-               throw MackieControlException( os.str() );
+               cout << e.what() << endl;
        }
-       
-       return (*it)->strip();
 }
 
-/////////////////////////////////////////////////
-// handlers for Route signals
-// TODO should these be part of RouteSignal?
-/////////////////////////////////////////////////
-
-void MackieControlProtocol::notify_solo_changed( ARDOUR::Route * route, MackiePort * port )
+void MackieControlProtocol::notify_mute_changed( RouteSignal * route_signal )
 {
        try
        {
-               Button & button = strip_from_route( route ).solo();
-               port->write( builder.build_led( button, route->soloed() ) );
+               Button & button = route_signal->strip().mute();
+               route_signal->port().write( builder.build_led( button, route_signal->route()->muted() ) );
        }
        catch( exception & e )
        {
@@ -954,12 +985,12 @@ void MackieControlProtocol::notify_solo_changed( ARDOUR::Route * route, MackiePo
        }
 }
 
-void MackieControlProtocol::notify_mute_changed( ARDOUR::Route * route, MackiePort * port )
+void MackieControlProtocol::notify_record_enable_changed( RouteSignal * route_signal )
 {
        try
        {
-               Button & button = strip_from_route( route ).mute();
-               port->write( builder.build_led( button, route->muted() ) );
+               Button & button = route_signal->strip().recenable();
+               route_signal->port().write( builder.build_led( button, route_signal->route()->record_enabled() ) );
        }
        catch( exception & e )
        {
@@ -967,12 +998,14 @@ void MackieControlProtocol::notify_mute_changed( ARDOUR::Route * route, MackiePo
        }
 }
 
-void MackieControlProtocol::notify_record_enable_changed( ARDOUR::Route * route, MackiePort * port )
+void MackieControlProtocol::notify_active_changed (RouteSignal *)
 {
        try
        {
-               Button & button = strip_from_route( route ).recenable();
-               port->write( builder.build_led( button, route->record_enabled() ) );
+#ifdef DEBUG
+               cout << "MackieControlProtocol::notify_active_changed" << endl;
+#endif
+               refresh_current_bank();
        }
        catch( exception & e )
        {
@@ -980,14 +1013,20 @@ void MackieControlProtocol::notify_record_enable_changed( ARDOUR::Route * route,
        }
 }
 
-void MackieControlProtocol::notify_gain_changed(  ARDOUR::Route * route, MackiePort * port )
+void MackieControlProtocol::notify_gain_changed( RouteSignal * route_signal, bool force_update )
 {
        try
        {
-               Fader & fader = strip_from_route( route ).gain();
-               if ( !fader.touch() )
+               Fader & fader = route_signal->strip().gain();
+               if ( !fader.in_use() )
                {
-                       port->write( builder.build_fader( fader, gain_to_slider_position( route->gain() ) ) );
+                       float gain_value = route_signal->route()->gain_control()->get_value();
+                       // check that something has actually changed
+                       if ( force_update || gain_value != route_signal->last_gain_written() )
+                       {
+                               route_signal->port().write( builder.build_fader( fader, gain_value ) );
+                               route_signal->last_gain_written( gain_value );
+                       }
                }
        }
        catch( exception & e )
@@ -996,11 +1035,29 @@ void MackieControlProtocol::notify_gain_changed(  ARDOUR::Route * route, MackieP
        }
 }
 
-void MackieControlProtocol::notify_name_changed( void *, ARDOUR::Route * route, MackiePort * port )
+void MackieControlProtocol::notify_name_changed( RouteSignal * route_signal )
 {
        try
        {
-               // TODO implement MackieControlProtocol::notify_name_changed
+               Strip & strip = route_signal->strip();
+               if ( !strip.is_master() )
+               {
+                       string line1;
+                       string fullname = route_signal->route()->name();
+
+                       if ( fullname.length() <= 6 )
+                       {
+                               line1 = fullname;
+                       }
+                       else
+                       {
+                               line1 = PBD::short_version( fullname, 6 );
+                       }
+
+                       SurfacePort & port = route_signal->port();
+                       port.write( builder.strip_display( port, strip, 0, line1 ) );
+                       port.write( builder.strip_display_blank( port, strip, 1 ) );
+               }
        }
        catch( exception & e )
        {
@@ -1008,21 +1065,31 @@ void MackieControlProtocol::notify_name_changed( void *, ARDOUR::Route * route,
        }
 }
 
-// TODO deal with > 1 channel being panned
-void MackieControlProtocol::notify_panner_changed( ARDOUR::Route * route, MackiePort * port )
+void MackieControlProtocol::notify_panner_changed( RouteSignal * route_signal, bool force_update )
 {
        try
        {
-               Pot & pot = strip_from_route( route ).vpot();
-               if ( route->panner().size() == 1 )
+               Pot & pot = route_signal->strip().vpot();
+               boost::shared_ptr<Panner> panner = route_signal->route()->panner();
+               if ( (panner && panner->npanners() == 1) || ( panner->npanners() == 2 && panner->linked() ) )
                {
                        float pos;
-                       route->panner()[0]->get_effective_position( pos);
-                       port->write( builder.build_led_ring( pot, ControlState( on, pos ) ) );
+                       route_signal->route()->panner()->streampanner(0).get_effective_position( pos );
+
+                       // cache the MidiByteArray here, because the mackie led control is much lower
+                       // resolution than the panner control. So we save lots of byte
+                       // sends in spite of more work on the comparison
+                       MidiByteArray bytes = builder.build_led_ring( pot, ControlState( on, pos ), MackieMidiBuilder::midi_pot_mode_dot );
+                       // check that something has actually changed
+                       if ( force_update || bytes != route_signal->last_pan_written() )
+                       {
+                               route_signal->port().write( bytes );
+                               route_signal->last_pan_written( bytes );
+                       }
                }
                else
                {
-                       port->write( builder.zero_control( pot ) );
+                       route_signal->port().write( builder.zero_control( pot ) );
                }
        }
        catch( exception & e )
@@ -1031,26 +1098,163 @@ void MackieControlProtocol::notify_panner_changed( ARDOUR::Route * route, Mackie
        }
 }
 
+// TODO handle plugin automation polling
+void MackieControlProtocol::update_automation( RouteSignal & rs )
+{
+       ARDOUR::AutoState gain_state = rs.route()->gain_control()->automation_state();
+       if ( gain_state == Touch || gain_state == Play )
+       {
+               notify_gain_changed( &rs, false );
+       }
+
+       if ( rs.route()->panner() ) {
+               ARDOUR::AutoState panner_state = rs.route()->panner()->automation_state();
+               if ( panner_state == Touch || panner_state == Play )
+               {
+                       notify_panner_changed( &rs, false );
+               }
+       }
+       _automation_last.start();
+}
+
+string MackieControlProtocol::format_bbt_timecode( nframes_t now_frame )
+{
+       BBT_Time bbt_time;
+       session->bbt_time( now_frame, bbt_time );
+
+       // According to the Logic docs
+       // digits: 888/88/88/888
+       // BBT mode: Bars/Beats/Subdivisions/Ticks
+       ostringstream os;
+       os << setw(3) << setfill('0') << bbt_time.bars;
+       os << setw(2) << setfill('0') << bbt_time.beats;
+
+       // figure out subdivisions per beat
+       const Meter & meter = session->tempo_map().meter_at( now_frame );
+       int subdiv = 2;
+       if ( meter.note_divisor() == 8 && (meter.beats_per_bar() == 12.0 || meter.beats_per_bar() == 9.0 || meter.beats_per_bar() == 6.0) )
+       {
+               subdiv = 3;
+       }
+
+       uint32_t subdivisions = bbt_time.ticks / uint32_t( Meter::ticks_per_beat / subdiv );
+       uint32_t ticks = bbt_time.ticks % uint32_t( Meter::ticks_per_beat / subdiv );
+
+       os << setw(2) << setfill('0') << subdivisions + 1;
+       os << setw(3) << setfill('0') << ticks;
+
+       return os.str();
+}
+
+string MackieControlProtocol::format_timecode_timecode( nframes_t now_frame )
+{
+       Timecode::Time timecode;
+       session->timecode_time( now_frame, timecode );
+
+       // According to the Logic docs
+       // digits: 888/88/88/888
+       // Timecode mode: Hours/Minutes/Seconds/Frames
+       ostringstream os;
+       os << setw(3) << setfill('0') << timecode.hours;
+       os << setw(2) << setfill('0') << timecode.minutes;
+       os << setw(2) << setfill('0') << timecode.seconds;
+       os << setw(3) << setfill('0') << timecode.frames;
+
+       return os.str();
+}
+
+void MackieControlProtocol::update_timecode_display()
+{
+       if ( surface().has_timecode_display() )
+       {
+               // do assignment here so current_frame is fixed
+               nframes_t current_frame = session->transport_frame();
+               string timecode;
+
+               switch ( _timecode_type )
+               {
+                       case ARDOUR::AnyTime::BBT:
+                               timecode = format_bbt_timecode( current_frame );
+                               break;
+                       case ARDOUR::AnyTime::Timecode:
+                               timecode = format_timecode_timecode( current_frame );
+                               break;
+                       default:
+                               ostringstream os;
+                               os << "Unknown timecode: " << _timecode_type;
+                               throw runtime_error( os.str() );
+               }
+
+               // only write the timecode string to the MCU if it's changed
+               // since last time. This is to reduce midi bandwidth used.
+               if ( timecode != _timecode_last )
+               {
+                       surface().display_timecode( mcu_port(), builder, timecode, _timecode_last );
+                       _timecode_last = timecode;
+               }
+       }
+}
+
+void MackieControlProtocol::poll_session_data()
+{
+       if ( _active && _automation_last.elapsed() >= 20 )
+       {
+               // do all currently mapped routes
+               for( RouteSignals::iterator it = route_signals.begin(); it != route_signals.end(); ++it )
+               {
+                       update_automation( **it );
+               }
+
+               // and the master strip
+               if ( master_route_signal != 0 )
+               {
+                       update_automation( *master_route_signal );
+               }
+
+               update_timecode_display();
+
+               _automation_last.start();
+       }
+}
+
 /////////////////////////////////////
 // Transport Buttons
 /////////////////////////////////////
 
-LedState MackieControlProtocol::rewind_press( Button & button )
+LedState MackieControlProtocol::frm_left_press (Button &)
 {
        // can use first_mark_before/after as well
+       unsigned long elapsed = _frm_left_last.restart();
+
        Location * loc = session->locations()->first_location_before (
                session->transport_frame()
        );
-       if ( loc != 0 ) session->request_locate( loc->start(), session->transport_rolling() );
+
+       // allow a quick double to go past a previous mark
+       if ( session->transport_rolling() && elapsed < 500 && loc != 0 )
+       {
+               Location * loc_two_back = session->locations()->first_location_before ( loc->start() );
+               if ( loc_two_back != 0 )
+               {
+                       loc = loc_two_back;
+               }
+       }
+
+       // move to the location, if it's valid
+       if ( loc != 0 )
+       {
+               session->request_locate( loc->start(), session->transport_rolling() );
+       }
+
        return on;
 }
 
-LedState MackieControlProtocol::rewind_release( Button & button )
+LedState MackieControlProtocol::frm_left_release (Button &)
 {
        return off;
 }
 
-LedState MackieControlProtocol::ffwd_press( Button & button )
+LedState MackieControlProtocol::frm_right_press (Button &)
 {
        // can use first_mark_before/after as well
        Location * loc = session->locations()->first_location_after (
@@ -1060,34 +1264,34 @@ LedState MackieControlProtocol::ffwd_press( Button & button )
        return on;
 }
 
-LedState MackieControlProtocol::ffwd_release( Button & button )
+LedState MackieControlProtocol::frm_right_release (Button &)
 {
        return off;
 }
 
-LedState MackieControlProtocol::stop_press( Button & button )
+LedState MackieControlProtocol::stop_press (Button &)
 {
        session->request_stop();
        return on;
 }
 
-LedState MackieControlProtocol::stop_release( Button & button )
+LedState MackieControlProtocol::stop_release (Button &)
 {
        return session->transport_stopped();
 }
 
-LedState MackieControlProtocol::play_press( Button & button )
+LedState MackieControlProtocol::play_press (Button &)
 {
        session->request_transport_speed( 1.0 );
        return on;
 }
 
-LedState MackieControlProtocol::play_release( Button & button )
+LedState MackieControlProtocol::play_release (Button &)
 {
        return session->transport_rolling();
 }
 
-LedState MackieControlProtocol::record_press( Button & button )
+LedState MackieControlProtocol::record_press (Button &)
 {
        if ( session->get_record_enabled() )
                session->disable_record( false );
@@ -1096,7 +1300,7 @@ LedState MackieControlProtocol::record_press( Button & button )
        return on;
 }
 
-LedState MackieControlProtocol::record_release( Button & button )
+LedState MackieControlProtocol::record_release (Button &)
 {
        if ( session->get_record_enabled() )
        {
@@ -1109,25 +1313,153 @@ LedState MackieControlProtocol::record_release( Button & button )
                return off;
 }
 
+LedState MackieControlProtocol::rewind_press (Button &)
+{
+       _jog_wheel.push( JogWheel::speed );
+       _jog_wheel.transport_direction( -1 );
+       session->request_transport_speed( -_jog_wheel.transport_speed() );
+       return on;
+}
+
+LedState MackieControlProtocol::rewind_release (Button &)
+{
+       _jog_wheel.pop();
+       _jog_wheel.transport_direction( 0 );
+       if ( _transport_previously_rolling )
+               session->request_transport_speed( 1.0 );
+       else
+               session->request_stop();
+       return off;
+}
+
+LedState MackieControlProtocol::ffwd_press (Button &)
+{
+       _jog_wheel.push( JogWheel::speed );
+       _jog_wheel.transport_direction( 1 );
+       session->request_transport_speed( _jog_wheel.transport_speed() );
+       return on;
+}
+
+LedState MackieControlProtocol::ffwd_release (Button &)
+{
+       _jog_wheel.pop();
+       _jog_wheel.transport_direction( 0 );
+       if ( _transport_previously_rolling )
+               session->request_transport_speed( 1.0 );
+       else
+               session->request_stop();
+       return off;
+}
+
+LedState MackieControlProtocol::loop_press (Button &)
+{
+       session->request_play_loop( !session->get_play_loop() );
+       return on;
+}
+
+LedState MackieControlProtocol::loop_release (Button &)
+{
+       return session->get_play_loop();
+}
+
+LedState MackieControlProtocol::punch_in_press (Button &)
+{
+       bool state = !session->config.get_punch_in();
+       session->config.set_punch_in( state );
+       return state;
+}
+
+LedState MackieControlProtocol::punch_in_release (Button &)
+{
+       return session->config.get_punch_in();
+}
+
+LedState MackieControlProtocol::punch_out_press (Button &)
+{
+       bool state = !session->config.get_punch_out();
+       session->config.set_punch_out( state );
+       return state;
+}
+
+LedState MackieControlProtocol::punch_out_release (Button &)
+{
+       return session->config.get_punch_out();
+}
+
+LedState MackieControlProtocol::home_press (Button &)
+{
+       session->goto_start();
+       return on;
+}
+
+LedState MackieControlProtocol::home_release (Button &)
+{
+       return off;
+}
+
+LedState MackieControlProtocol::end_press (Button &)
+{
+       session->goto_end();
+       return on;
+}
+
+LedState MackieControlProtocol::end_release (Button &)
+{
+       return off;
+}
+
+LedState MackieControlProtocol::clicking_press (Button &)
+{
+       bool state = !Config->get_clicking();
+       Config->set_clicking( state );
+       return state;
+}
+
+LedState MackieControlProtocol::clicking_release (Button &)
+{
+       return Config->get_clicking();
+}
+
+LedState MackieControlProtocol::global_solo_press (Button &)
+{
+       bool state = !session->soloing();
+       session->set_solo (session->get_routes(), state);
+       return state;
+}
+
+LedState MackieControlProtocol::global_solo_release (Button &)
+{
+       return session->soloing();
+}
+
 ///////////////////////////////////////////
 // Session signals
 ///////////////////////////////////////////
 
-void MackieControlProtocol::notify_parameter_changed( const char * name_str )
+void MackieControlProtocol::notify_parameter_changed (std::string const & p)
 {
-       string name( name_str );
-       if ( name == "punch-in" )
+       if ( p == "punch-in" )
+       {
+               update_global_button( "punch_in", session->config.get_punch_in() );
+       }
+       else if ( p == "punch-out" )
+       {
+               update_global_button( "punch_out", session->config.get_punch_out() );
+       }
+       else if ( p == "clicking" )
        {
-               update_global_button( "punch_in", Config->get_punch_in() );
+               update_global_button( "clicking", Config->get_clicking() );
        }
-       else if ( name == "punch-out" )
+       else
        {
-               update_global_button( "punch_out", Config->get_punch_out() );
+#ifdef DEBUG
+               cout << "parameter changed: " << p << endl;
+#endif
        }
 }
 
 // RouteList is the set of routes that have just been added
-void MackieControlProtocol::notify_route_added( ARDOUR::Session::RouteList & rl )
+void MackieControlProtocol::notify_route_added( ARDOUR::RouteList & rl )
 {
        // currently assigned banks are less than the full set of
        // strips, so activate the new strip now.
@@ -1136,12 +1468,12 @@ void MackieControlProtocol::notify_route_added( ARDOUR::Session::RouteList & rl
                refresh_current_bank();
        }
        // otherwise route added, but current bank needs no updating
-       
+
        // make sure remote id changes in the new route are handled
-       typedef ARDOUR::Session::RouteList ARS;
+       typedef ARDOUR::RouteList ARS;
        for ( ARS::iterator it = rl.begin(); it != rl.end(); ++it )
        {
-               (*it)->RemoteControlIDChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_remote_id_changed) ) );
+               connections_back = (*it)->RemoteControlIDChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_remote_id_changed) ) );
        }
 }
 
@@ -1154,13 +1486,13 @@ void MackieControlProtocol::notify_solo_active_changed( bool active )
 void MackieControlProtocol::notify_remote_id_changed()
 {
        Sorted sorted = get_sorted_routes();
-       
+
        // if a remote id has been moved off the end, we need to shift
        // the current bank backwards.
        if ( sorted.size() - _current_initial_bank < route_signals.size() )
        {
                // but don't shift backwards past the zeroth channel
-               switch_banks( max( (unsigned int)0, sorted.size() - route_signals.size() ) );
+               switch_banks( max((Sorted::size_type) 0, sorted.size() - route_signals.size() ) );
        }
        // Otherwise just refresh the current bank
        else
@@ -1186,73 +1518,18 @@ void MackieControlProtocol::notify_transport_state_changed()
        update_global_button( "play", session->transport_rolling() );
        update_global_button( "stop", !session->transport_rolling() );
        update_global_button( "loop", session->get_play_loop() );
-       
+
+       _transport_previously_rolling = session->transport_rolling();
+
        // rec is special because it's tristate
        Button * rec = reinterpret_cast<Button*>( surface().controls_by_name["record"] );
        mcu_port().write( builder.build_led( *rec, record_release( *rec ) ) );
 }
 
-LedState MackieControlProtocol::loop_press( Button & button )
-{
-       session->request_play_loop( !session->get_play_loop() );
-       return on;
-}
-
-LedState MackieControlProtocol::loop_release( Button & button )
-{
-       return session->get_play_loop();
-}
-
-LedState MackieControlProtocol::punch_in_press( Button & button )
-{
-       bool state = !Config->get_punch_in();
-       Config->set_punch_in( state );
-       return state;
-}
-
-LedState MackieControlProtocol::punch_in_release( Button & button )
-{
-       return Config->get_punch_in();
-}
-
-LedState MackieControlProtocol::punch_out_press( Button & button )
-{
-       bool state = !Config->get_punch_out();
-       Config->set_punch_out( state );
-       return state;
-}
-
-LedState MackieControlProtocol::punch_out_release( Button & button )
-{
-       return Config->get_punch_out();
-}
-
-LedState MackieControlProtocol::home_press( Button & button )
-{
-       session->goto_start();
-       return on;
-}
-
-LedState MackieControlProtocol::home_release( Button & button )
-{
-       return off;
-}
-
-LedState MackieControlProtocol::end_press( Button & button )
-{
-       session->goto_end();
-       return on;
-}
-
-LedState MackieControlProtocol::end_release( Button & button )
-{
-       return off;
-}
-
 /////////////////////////////////////
 // Bank Switching
 /////////////////////////////////////
-LedState MackieControlProtocol::left_press( Button & button )
+LedState MackieControlProtocol::left_press (Button &)
 {
        Sorted sorted = get_sorted_routes();
        if ( sorted.size() > route_table.size() )
@@ -1264,7 +1541,7 @@ LedState MackieControlProtocol::left_press( Button & button )
                        session->set_dirty();
                        switch_banks( new_initial );
                }
-               
+
                return on;
        }
        else
@@ -1273,12 +1550,12 @@ LedState MackieControlProtocol::left_press( Button & button )
        }
 }
 
-LedState MackieControlProtocol::left_release( Button & button )
+LedState MackieControlProtocol::left_release (Button &)
 {
        return off;
 }
 
-LedState MackieControlProtocol::right_press( Button & button )
+LedState MackieControlProtocol::right_press (Button &)
 {
        Sorted sorted = get_sorted_routes();
        if ( sorted.size() > route_table.size() )
@@ -1290,7 +1567,7 @@ LedState MackieControlProtocol::right_press( Button & button )
                        session->set_dirty();
                        switch_banks( _current_initial_bank + delta );
                }
-               
+
                return on;
        }
        else
@@ -1299,12 +1576,12 @@ LedState MackieControlProtocol::right_press( Button & button )
        }
 }
 
-LedState MackieControlProtocol::right_release( Button & button )
+LedState MackieControlProtocol::right_release (Button &)
 {
        return off;
 }
 
-LedState MackieControlProtocol::channel_left_press( Button & button )
+LedState MackieControlProtocol::channel_left_press (Button &)
 {
        Sorted sorted = get_sorted_routes();
        if ( sorted.size() > route_table.size() )
@@ -1318,12 +1595,12 @@ LedState MackieControlProtocol::channel_left_press( Button & button )
        }
 }
 
-LedState MackieControlProtocol::channel_left_release( Button & button )
+LedState MackieControlProtocol::channel_left_release (Button &)
 {
        return off;
 }
 
-LedState MackieControlProtocol::channel_right_press( Button & button )
+LedState MackieControlProtocol::channel_right_press (Button &)
 {
        Sorted sorted = get_sorted_routes();
        if ( sorted.size() > route_table.size() )
@@ -1337,7 +1614,124 @@ LedState MackieControlProtocol::channel_right_press( Button & button )
        }
 }
 
-LedState MackieControlProtocol::channel_right_release( Button & button )
+LedState MackieControlProtocol::channel_right_release (Button &)
+{
+       return off;
+}
+
+/////////////////////////////////////
+// Functions
+/////////////////////////////////////
+LedState MackieControlProtocol::marker_press (Button &)
+{
+       // cut'n'paste from LocationUI::add_new_location()
+       string markername;
+       nframes_t where = session->audible_frame();
+       session->locations()->next_available_name(markername,"mcu");
+       Location *location = new Location (where, where, markername, Location::IsMark);
+       session->begin_reversible_command (_("add marker"));
+       XMLNode &before = session->locations()->get_state();
+       session->locations()->add (location, true);
+       XMLNode &after = session->locations()->get_state();
+       session->add_command (new MementoCommand<Locations>(*(session->locations()), &before, &after));
+       session->commit_reversible_command ();
+       return on;
+}
+
+LedState MackieControlProtocol::marker_release (Button &)
+{
+       return off;
+}
+
+void jog_wheel_state_display( JogWheel::State state, SurfacePort & port )
+{
+       switch( state )
+       {
+               case JogWheel::zoom: port.write( builder.two_char_display( "Zm" ) ); break;
+               case JogWheel::scroll: port.write( builder.two_char_display( "Sc" ) ); break;
+               case JogWheel::scrub: port.write( builder.two_char_display( "Sb" ) ); break;
+               case JogWheel::shuttle: port.write( builder.two_char_display( "Sh" ) ); break;
+               case JogWheel::speed: port.write( builder.two_char_display( "Sp" ) ); break;
+               case JogWheel::select: port.write( builder.two_char_display( "Se" ) ); break;
+       }
+}
+
+Mackie::LedState MackieControlProtocol::zoom_press( Mackie::Button & )
+{
+       _jog_wheel.zoom_state_toggle();
+       update_global_button( "scrub", _jog_wheel.jog_wheel_state() == JogWheel::scrub );
+       jog_wheel_state_display( _jog_wheel.jog_wheel_state(), mcu_port() );
+       return _jog_wheel.jog_wheel_state() == JogWheel::zoom;
+}
+
+Mackie::LedState MackieControlProtocol::zoom_release( Mackie::Button & )
+{
+       return _jog_wheel.jog_wheel_state() == JogWheel::zoom;
+}
+
+Mackie::LedState MackieControlProtocol::scrub_press( Mackie::Button & )
+{
+       _jog_wheel.scrub_state_cycle();
+       update_global_button( "zoom", _jog_wheel.jog_wheel_state() == JogWheel::zoom );
+       jog_wheel_state_display( _jog_wheel.jog_wheel_state(), mcu_port() );
+       return
+               _jog_wheel.jog_wheel_state() == JogWheel::scrub
+               ||
+               _jog_wheel.jog_wheel_state() == JogWheel::shuttle
+       ;
+}
+
+Mackie::LedState MackieControlProtocol::scrub_release( Mackie::Button & )
+{
+       return
+               _jog_wheel.jog_wheel_state() == JogWheel::scrub
+               ||
+               _jog_wheel.jog_wheel_state() == JogWheel::shuttle
+       ;
+}
+
+LedState MackieControlProtocol::drop_press (Button &)
+{
+       session->remove_last_capture();
+       return on;
+}
+
+LedState MackieControlProtocol::drop_release (Button &)
+{
+       return off;
+}
+
+LedState MackieControlProtocol::save_press (Button &)
+{
+       session->save_state( "" );
+       return on;
+}
+
+LedState MackieControlProtocol::save_release (Button &)
+{
+       return off;
+}
+
+LedState MackieControlProtocol::timecode_beats_press (Button &)
+{
+       switch ( _timecode_type )
+       {
+               case ARDOUR::AnyTime::BBT:
+                       _timecode_type = ARDOUR::AnyTime::Timecode;
+                       break;
+               case ARDOUR::AnyTime::Timecode:
+                       _timecode_type = ARDOUR::AnyTime::BBT;
+                       break;
+               default:
+                       ostringstream os;
+                       os << "Unknown Anytime::Type " << _timecode_type;
+                       throw runtime_error( os.str() );
+       }
+       update_timecode_beats_led();
+       return on;
+}
+
+LedState MackieControlProtocol::timecode_beats_release( Button & )
 {
        return off;
 }