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>
#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"
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()
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.
current_max += (*it)->strips();
if ( index < current_max ) return **it;
}
-
+
// oops - no matching port
ostringstream os;
os << "No port for index " << index;
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()
)
{
{
// 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() )
{
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.
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 )
{
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;
}
//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
}
}
// 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();
}
}
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()
}
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_";
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()
{
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.
}
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
}
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)
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 )
{
}
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() )
{
{
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() )
{
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
{
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
}
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 )
{
}
}
-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 )
{
}
}
-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 )
{
}
}
-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 )
}
}
-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 )
{
}
}
-// 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 )
}
}
+// 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 (
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 );
return on;
}
-LedState MackieControlProtocol::record_release( Button & button )
+LedState MackieControlProtocol::record_release (Button &)
{
if ( session->get_record_enabled() )
{
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.
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) ) );
}
}
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
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() )
session->set_dirty();
switch_banks( new_initial );
}
-
+
return on;
}
else
}
}
-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() )
session->set_dirty();
switch_banks( _current_initial_bank + delta );
}
-
+
return on;
}
else
}
}
-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() )
}
}
-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() )
}
}
-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;
}