X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fmackie%2Fmackie_control_protocol.cc;h=d7fda8ad7f2e79db1a038330906b0e68d230a7cb;hb=f387e834fd8966eff5f71955f013a905db9f7153;hp=5ef28d4549b6e7685dcee77c58ad96d901bb9fd0;hpb=449aab3c465bbbf66d221fac3d7ea559f1720357;p=ardour.git diff --git a/libs/surfaces/mackie/mackie_control_protocol.cc b/libs/surfaces/mackie/mackie_control_protocol.cc index 5ef28d4549..d7fda8ad7f 100644 --- a/libs/surfaces/mackie/mackie_control_protocol.cc +++ b/libs/surfaces/mackie/mackie_control_protocol.cc @@ -8,22 +8,22 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ +#include #include #include #include #include #include +#include -#define __STDC_FORMAT_MACROS #include #include #include @@ -32,1418 +32,1423 @@ #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include +#include "midi++/types.h" +#include "midi++/port.h" +#include "pbd/pthread_utils.h" +#include "pbd/error.h" +#include "pbd/memento_command.h" +#include "pbd/convert.h" + +#include "ardour/dB.h" +#include "ardour/debug.h" +#include "ardour/location.h" +#include "ardour/meter.h" +#include "ardour/panner.h" +#include "ardour/panner_shell.h" +#include "ardour/route.h" +#include "ardour/session.h" +#include "ardour/tempo.h" +#include "ardour/types.h" +#include "ardour/audioengine.h" #include "mackie_control_protocol.h" #include "midi_byte_array.h" #include "mackie_control_exception.h" -#include "route_signal.h" #include "mackie_midi_builder.h" #include "surface_port.h" #include "surface.h" -#include "bcf_surface.h" -#include "mackie_surface.h" + +#include "strip.h" +#include "control_group.h" +#include "meter.h" +#include "button.h" +#include "fader.h" +#include "pot.h" using namespace ARDOUR; using namespace std; -using namespace sigc; using namespace Mackie; using namespace PBD; - -using boost::shared_ptr; +using namespace Glib; #include "i18n.h" -MackieMidiBuilder builder; +#include "pbd/abstract_ui.cc" // instantiate template -// 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); -} +#define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__)) -/* - 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) +const int MackieControlProtocol::MODIFIER_OPTION = 0x1; +const int MackieControlProtocol::MODIFIER_CONTROL = 0x2; +const int MackieControlProtocol::MODIFIER_SHIFT = 0x3; +const int MackieControlProtocol::MODIFIER_CMDALT = 0x4; + +MackieControlProtocol* MackieControlProtocol::_instance = 0; + +bool MackieControlProtocol::probe() { - /* 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); + return true; } 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 ) + : ControlProtocol (session, X_("Mackie"), this) + , AbstractUI ("mackie") + , _current_initial_bank (0) + , _timecode_type (ARDOUR::AnyTime::BBT) + , _input_bundle (new ARDOUR::Bundle (_("Mackie Control In"), true)) + , _output_bundle (new ARDOUR::Bundle (_("Mackie Control Out"), false)) + , _gui (0) + , _zoom_mode (false) + , _scrub_mode (false) + , _flip_mode (false) + , _current_selected_track (-1) { - //cout << "MackieControlProtocol::MackieControlProtocol" << endl; - // will start reading from ports, as soon as there are some - pthread_create_and_store (X_("mackie monitor"), &thread, 0, _monitor_work, this); + DEBUG_TRACE (DEBUG::MackieControl, "MackieControlProtocol::MackieControlProtocol\n"); + + AudioEngine::instance()->PortConnectedOrDisconnected.connect ( + audio_engine_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::port_connected_or_disconnected, this, _2, _4, _5), + this + ); + + _instance = this; } MackieControlProtocol::~MackieControlProtocol() { - //cout << "~MackieControlProtocol::MackieControlProtocol" << endl; - try - { + DEBUG_TRACE (DEBUG::MackieControl, "MackieControlProtocol::~MackieControlProtocol\n"); + + _active = false; + + try { close(); } - catch ( exception & e ) - { + catch (exception & e) { cout << "~MackieControlProtocol caught " << e.what() << endl; } - catch ( ... ) - { + catch (...) { cout << "~MackieControlProtocol caught unknown" << endl; } - //cout << "finished ~MackieControlProtocol::MackieControlProtocol" << endl; -} -Mackie::Surface & MackieControlProtocol::surface() -{ - if ( _surface == 0 ) - { - throw MackieControlException( "_surface is 0 in MackieControlProtocol::surface" ); - } - return *_surface; -} + DEBUG_TRACE (DEBUG::MackieControl, "finished ~MackieControlProtocol::MackieControlProtocol\n"); -const Mackie::MackiePort & MackieControlProtocol::mcu_port() const -{ - return dynamic_cast( *_ports[0] ); + _instance = 0; } -Mackie::MackiePort & MackieControlProtocol::mcu_port() +void +MackieControlProtocol::thread_init () { - return dynamic_cast( *_ports[0] ); + struct sched_param rtparam; + + pthread_set_name (X_("MackieControl")); + + PBD::notify_gui_about_thread_creation (X_("gui"), pthread_self(), X_("MackieControl"), 2048); + ARDOUR::SessionEvent::create_per_thread_pool (X_("MackieControl"), 128); + + memset (&rtparam, 0, sizeof (rtparam)); + rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */ + + if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) { + // do we care? not particularly. + } } // go to the previous track. // Assume that get_sorted_routes().size() > route_table.size() -void MackieControlProtocol::prev_track() +void +MackieControlProtocol::prev_track() { - if ( _current_initial_bank >= 1 ) - { + if (_current_initial_bank >= 1) { session->set_dirty(); - switch_banks( _current_initial_bank - 1 ); + switch_banks (_current_initial_bank - 1); } } // go to the next track. // Assume that get_sorted_routes().size() > route_table.size() -void MackieControlProtocol::next_track() +void +MackieControlProtocol::next_track() { Sorted sorted = get_sorted_routes(); - if ( _current_initial_bank + route_table.size() < sorted.size() ) - { + if (_current_initial_bank + n_strips() < sorted.size()) { session->set_dirty(); - switch_banks( _current_initial_bank + 1 ); - } -} - -void MackieControlProtocol::clear_route_signals() -{ - for( RouteSignals::iterator it = route_signals.begin(); it != route_signals.end(); ++it ) - { - delete *it; - } - route_signals.clear(); -} - -// return the port for a given id - 0 based -// throws an exception if no port found -MackiePort & MackieControlProtocol::port_for_id( uint32_t index ) -{ - uint32_t current_max = 0; - for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) - { - current_max += (*it)->strips(); - if ( index < current_max ) return **it; + switch_banks (_current_initial_bank + 1); } - - // oops - no matching port - ostringstream os; - os << "No port for index " << index; - throw MackieControlException( os.str() ); } // predicate for sort call in get_sorted_routes struct RouteByRemoteId { - bool operator () ( const shared_ptr & a, const shared_ptr & b ) const + bool operator () (const boost::shared_ptr & a, const boost::shared_ptr & b) const { return a->remote_control_id() < b->remote_control_id(); } - bool operator () ( const Route & a, const Route & b ) const + bool operator () (const Route & a, const Route & b) const { return a.remote_control_id() < b.remote_control_id(); } - bool operator () ( const Route * a, const Route * b ) const + bool operator () (const Route * a, const Route * b) const { return a->remote_control_id() < b->remote_control_id(); } }; -MackieControlProtocol::Sorted MackieControlProtocol::get_sorted_routes() +MackieControlProtocol::Sorted +MackieControlProtocol::get_sorted_routes() { Sorted sorted; - + // fetch all routes - boost::shared_ptr routes = session->get_routes(); + boost::shared_ptr routes = session->get_routes(); set remote_ids; - + // routes with remote_id 0 should never be added // TODO verify this with ardour devs - // remote_ids.insert( 0 ); - + // 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.is_master() - && !route.is_hidden() - && !route.is_control() - && remote_ids.find( route.remote_control_id() ) == remote_ids.end() - ) - { - sorted.push_back( *it ); - remote_ids.insert( route.remote_control_id() ); + route.active() + && !route.is_master() + && !route.is_hidden() + && !route.is_monitor() + && remote_ids.find (route.remote_control_id()) == remote_ids.end() + ) { + sorted.push_back (*it); + remote_ids.insert (route.remote_control_id()); } } - sort( sorted.begin(), sorted.end(), RouteByRemoteId() ); + sort (sorted.begin(), sorted.end(), RouteByRemoteId()); return sorted; } -void MackieControlProtocol::refresh_current_bank() +void +MackieControlProtocol::refresh_current_bank() { - switch_banks( _current_initial_bank ); + switch_banks (_current_initial_bank, true); } -void MackieControlProtocol::switch_banks( int initial ) +uint32_t +MackieControlProtocol::n_strips() const { - // DON'T prevent bank switch if initial == _current_initial_bank - // because then this method can't be used as a refresh - - // sanity checking + uint32_t strip_count = 0; + + for (Surfaces::const_iterator si = surfaces.begin(); si != surfaces.end(); ++si) { + if ((*si)->active()) { + strip_count += (*si)->n_strips (); + } + } + + return strip_count; +} + +void +MackieControlProtocol::switch_banks (uint32_t initial, bool force) +{ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("switch banking to start at %1 force ? %2 current = %3\n", initial, force, _current_initial_bank)); + + if (initial == _current_initial_bank && !force) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("not switching to %1\n", initial)); + return; + } + 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 + uint32_t strip_cnt = n_strips(); + + if (sorted.size() <= strip_cnt && !force) { + /* no banking */ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("not switching to %1\n", initial)); return; } + + uint32_t delta = sorted.size() - strip_cnt; + + if (delta > 0 && initial > delta) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("not switching to %1\n", initial)); + 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() ) - { - // fetch the bank start and end to switch to - 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; - + _current_selected_track = -1; + + for (Surfaces::iterator si = surfaces.begin(); si != surfaces.end(); ++si) { + (*si)->drop_routes (); + } + + // Map current bank of routes onto each surface(+strip) + + if (_current_initial_bank <= sorted.size()) { + + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("switch to %1, %2, available routes %3\n", _current_initial_bank, strip_cnt, sorted.size())); + // link routes to strips - uint32_t i = 0; - for ( ; it != end && it != sorted.end(); ++it, ++i ) - { - boost::shared_ptr 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; - route_table[i] = route; - RouteSignal * rs = new RouteSignal( *route, *this, strip, port_for_id(i) ); - route_signals.push_back( rs ); - // update strip from route - rs->notify_all(); - } + + Sorted::iterator r = sorted.begin() + _current_initial_bank; - // 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 ) ); + for (Surfaces::iterator si = surfaces.begin(); si != surfaces.end(); ++si) { + vector > routes; + uint32_t added = 0; + + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("surface has %1 strips\n", (*si)->n_strips())); + + for (; r != sorted.end() && added < (*si)->n_strips(); ++r, ++added) { + routes.push_back (*r); + cerr << "\t\tadded " << (*r)->name() << endl; + } + + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("give surface %1 routes\n", routes.size())); + + (*si)->map_routes (routes); } } - + // 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 ) ); - } - } + surfaces.front()->display_bank_start (_current_initial_bank); } -void MackieControlProtocol::zero_all() +int +MackieControlProtocol::set_active (bool yn) { - // 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" ) ); - } - - // 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 ) ); - } - - // 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( control ), off ) ); - } - - // turn off global buttons and leds - // global buttons are only ever on mcu_port, so we don't have - // to figure out which port. - for ( Surface::Controls::iterator it = surface().controls.begin(); it != surface().controls.end(); ++it ) - { - Control & control = **it; - if ( !control.group().is_strip() && control.accepts_feedback() ) - { - mcu_port().write( builder.zero_control( control ) ); - } + if (yn == _active) { + return 0; } -} -int MackieControlProtocol::set_active (bool yn) -{ - if ( yn != _active ) + try { - try - { - // the reason for the locking and unlocking is that - // glibmm can't do a condition wait on a RecMutex - if ( yn ) - { - // TODO what happens if this fails half way? - - // 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 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 - { - close(); - _active = false; - } - } - catch( exception & e ) - { -#ifdef DEBUG - cout << "set_active to false because exception caught: " << e.what() << endl; -#endif + if (yn) { + + /* start event loop */ + + BaseUI::run (); + + create_surfaces (); + connect_session_signals (); + + _active = true; + update_surfaces (); + + /* set up periodic task for metering and automation + */ + + Glib::RefPtr periodic_timeout = Glib::TimeoutSource::create (100); // milliseconds + periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &MackieControlProtocol::periodic)); + periodic_timeout->attach (main_loop()->get_context()); + + } else { + BaseUI::quit (); + close(); _active = false; - throw; } } + + catch (exception & e) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("set_active to false because exception caught: %1\n", e.what())); + _active = false; + throw; + } return 0; } -bool MackieControlProtocol::handle_strip_button( Control & control, ButtonState bs, boost::shared_ptr route ) +bool +MackieControlProtocol::periodic () { - bool state = false; + if (!_active) { + return false; + } - if ( bs == press ) - { - if ( control.name() == "recenable" ) - { - state = !route->record_enabled(); - route->set_record_enable( state, this ); - } - else if ( control.name() == "mute" ) - { - state = !route->muted(); - route->set_mute( state, this ); - } - else if ( control.name() == "solo" ) - { - state = !route->soloed(); - route->set_solo( state, this ); - } - else if ( control.name() == "select" ) - { - // TODO make the track selected. Whatever that means. - //state = default_button_press( dynamic_cast( control ) ); - } - else if ( control.name() == "vselect" ) - { - // TODO could be used to select different things to apply the pot to? - //state = default_button_press( dynamic_cast( control ) ); - } + for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) { + (*s)->periodic (); } - if ( control.name() == "fader_touch" ) - { - state = bs == press; - control.strip().gain().touch( state ); + update_timecode_display(); + + return true; +} + + +void +MackieControlProtocol::update_timecode_beats_led() +{ + switch (_timecode_type) { + 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()); } - - return state; } -void MackieControlProtocol::update_led( Mackie::Button & button, Mackie::LedState ls ) +void +MackieControlProtocol::update_global_button (const string & name, LedState ls) { - MackiePort * port = 0; - if ( button.group().is_strip() ) - { - if ( button.group().is_master() ) - { - port = &mcu_port(); - } - else - { - port = &port_for_id( dynamic_cast( button.group() ).index() ); - } + boost::shared_ptr surface = surfaces.front(); + + if (!surface->type() == mcu) { + return; } - else - { - port = &mcu_port(); + + if (surface->controls_by_name.find (name) != surface->controls_by_name.end()) { + Button * button = dynamic_cast (surface->controls_by_name[name]); + surface->write (builder.build_led (button->led(), ls)); + } else { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Button %1 not found\n", name)); } - if ( ls != none ) port->write( builder.build_led( button, ls ) ); } -void MackieControlProtocol::update_global_button( const string & name, LedState ls ) +void +MackieControlProtocol::update_global_led (const string & name, LedState ls) { - if ( surface().controls_by_name.find( name ) !=surface().controls_by_name.end() ) - { - Button * button = dynamic_cast( surface().controls_by_name[name] ); - mcu_port().write( builder.build_led( button->led(), ls ) ); + boost::shared_ptr surface = surfaces.front(); + + if (!surface->type() == mcu) { + return; + } + + if (surface->controls_by_name.find (name) != surface->controls_by_name.end()) { + Led * led = dynamic_cast (surface->controls_by_name[name]); + surface->write (builder.build_led (*led, ls)); + } else { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Led %1 not found\n", name)); } - else - { -#ifdef DEBUG - cout << "Button " << name << " not found" << endl; -#endif - } } // send messages to surface to set controls to correct values -void MackieControlProtocol::update_surface() +void +MackieControlProtocol::update_surfaces() { - if ( _active ) - { - // 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( new RouteSignal( *master_route(), *this, master_strip(), mcu_port() ) ); - // update strip from route - master_route_signal->notify_all(); - - // update global buttons and displays - notify_record_state_changed(); - notify_transport_state_changed(); + if (!_active) { + return; } + + // do the initial bank switch to connect signals + // _current_initial_bank is initialised by set_state + switch_banks (_current_initial_bank, true); + + // sometimes the jog wheel is a pot + surfaces.front()->blank_jog_ring (); + + // update global buttons and displays + + notify_record_state_changed(); + notify_transport_state_changed(); + update_timecode_beats_led(); } -void MackieControlProtocol::connect_session_signals() +void +MackieControlProtocol::connect_session_signals() { // receive routes added - connections_back = session->RouteAdded.connect( ( mem_fun (*this, &MackieControlProtocol::notify_route_added) ) ); + session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_route_added, this, _1), this); // receive record state toggled - connections_back = session->RecordStateChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_record_state_changed) ) ); + session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_record_state_changed, this), this); // receive transport state changed - connections_back = session->TransportStateChange.connect( ( mem_fun (*this, &MackieControlProtocol::notify_transport_state_changed) ) ); + session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_transport_state_changed, this), this); + session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_loop_state_changed, this), this); // receive punch-in and punch-out - connections_back = Config->ParameterChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_parameter_changed) ) ); + Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_parameter_changed, this, _1), this); + session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_parameter_changed, this, _1), this); // receive rude solo changed - connections_back = session->SoloActive.connect( ( mem_fun (*this, &MackieControlProtocol::notify_solo_active_changed) ) ); - + session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_solo_active_changed, this, _1), this); + // 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 ) - { - 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 ); - - 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_active) - , sport - ) - ); - - connections_back = sport->inactive_event.connect( - sigc::bind ( - mem_fun (*this, &MackieControlProtocol::handle_port_inactive) - , sport - ) - ); - - _ports_changed = true; -} - -void MackieControlProtocol::create_ports() -{ - MIDI::Manager * mm = MIDI::Manager::instance(); - - // open main port - { - MIDI::Port * midi_port = mm->port( default_port_name ); - if ( midi_port == 0 ) { - ostringstream os; - os << string_compose( _("no MIDI port named \"%1\" exists - Mackie control disabled"), default_port_name ); - error << os.str() << endmsg; - throw MackieControlException( os.str() ); - } - 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_"; - for ( int index = 1; index <= 9; ++index ) - { - ostringstream os; - os << ext_port_base << index; - MIDI::Port * midi_port = mm->port( os.str() ); - if ( midi_port != 0 ) add_port( *midi_port, index ); + for (Sorted::iterator it = sorted.begin(); it != sorted.end(); ++it) { + (*it)->RemoteControlIDChanged.connect (route_connections, MISSING_INVALIDATOR, ui_bind(&MackieControlProtocol::notify_remote_id_changed, this), this); } } -shared_ptr MackieControlProtocol::master_route() +void +MackieControlProtocol::create_surfaces () { - shared_ptr retval; - retval = session->route_by_name( "master" ); - if ( retval == 0 ) - { - // TODO search through all routes for one with the master attribute set - } - return retval; -} + string device_name = "mcu"; + surface_type_t stype = mcu; -Strip & MackieControlProtocol::master_strip() -{ - return dynamic_cast( *surface().groups["master"] ); -} + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Create %1 surfaces\n", + 1 + ARDOUR::Config->get_mackie_extenders())); -void MackieControlProtocol::initialize_surface() -{ - // set up the route table - int strips = 0; - for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) - { - strips += (*it)->strips(); - } - - set_route_table_size( strips ); - - switch ( mcu_port().emulation() ) - { - 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->init(); - - // Connect events. Must be after route table otherwise there will be trouble - for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) - { - connections_back = (*it)->control_event.connect( ( mem_fun (*this, &MackieControlProtocol::handle_control_event) ) ); - } -} + for (uint32_t n = 0; n < 1 + ARDOUR::Config->get_mackie_extenders(); ++n) { -void MackieControlProtocol::close() -{ - // 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::iterator it = _connections.begin(); it != _connections.end(); ++it ) - { - it->disconnect(); - } -#endif - - if ( _surface != 0 ) - { - // These will fail if the port has gone away. - // So catch the exception and do the rest of the - // close afterwards - // because the bcf doesn't respond to the next 3 sysex messages - try - { - zero_all(); - } - catch ( exception & e ) - { -#ifdef DEBUG - cout << "MackieControlProtocol::close caught exception: " << e.what() << endl; -#endif - } + boost::shared_ptr surface (new Surface (*this, session->engine().jack(), device_name, n, stype)); + surfaces.push_back (surface); - for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) - { - try - { - MackiePort & port = **it; - // faders to minimum - port.write_sysex( 0x61 ); - // All LEDs off - port.write_sysex( 0x62 ); - // Reset (reboot into offline mode) - port.write_sysex( 0x63 ); - } - catch ( exception & e ) - { -#ifdef DEBUG - cout << "MackieControlProtocol::close caught exception: " << e.what() << endl; -#endif - } - } + device_name = "mcu_xt"; + stype = ext; + + _input_bundle->add_channel ( + surface->port().input_port().name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (surface->port().input_port().name()) + ); - // disconnect routes from strips - clear_route_signals(); + _output_bundle->add_channel ( + surface->port().output_port().name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (surface->port().output_port().name()) + ); + + int fd; + MIDI::Port& input_port (surface->port().input_port()); - delete _surface; - _surface = 0; - } - - // stop polling, and wait for it... - _polling = false; - pthread_join( thread, 0 ); - - // shut down MackiePorts - for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) - { - delete *it; + if ((fd = input_port.selectable ()) >= 0) { + Glib::RefPtr psrc = IOSource::create (fd, IO_IN|IO_HUP|IO_ERR); + + psrc->connect (sigc::bind (sigc::mem_fun (this, &MackieControlProtocol::midi_input_handler), &input_port)); + psrc->attach (main_loop()->get_context()); + + // glibmm hack: for now, store only the GSource* + + port_sources.push_back (psrc->gobj()); + g_source_ref (psrc->gobj()); + } } - _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) +void +MackieControlProtocol::close() { - return static_cast(arg)->monitor_work (); + clear_ports (); + + port_connections.drop_connections (); + session_connections.drop_connections (); + route_connections.drop_connections (); + periodic_connection.disconnect (); + + surfaces.clear (); } -XMLNode & MackieControlProtocol::get_state() +XMLNode& +MackieControlProtocol::get_state() { - //cout << "MackieControlProtocol::get_state" << endl; - + DEBUG_TRACE (DEBUG::MackieControl, "MackieControlProtocol::get_state\n"); + // add name of protocol - XMLNode* node = new XMLNode( X_("Protocol") ); - node->add_property( X_("name"), _name ); - + XMLNode* node = new XMLNode (X_("Protocol")); + node->add_property (X_("name"), ARDOUR::ControlProtocol::_name); + // add current bank ostringstream os; os << _current_initial_bank; - node->add_property( X_("bank"), os.str() ); - + 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*/) { - //cout << "MackieControlProtocol::set_state: active " << _active << endl; + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieControlProtocol::set_state: active %1\n", _active)); + int retval = 0; - + // fetch current bank - if ( node.property( X_("bank") ) != 0 ) - { - string bank = node.property( X_("bank") )->value(); - try - { - set_active( true ); - uint32_t new_bank = atoi( bank.c_str() ); - if ( _current_initial_bank != new_bank ) switch_banks( new_bank ); - } - catch ( exception & e ) - { -#ifdef DEBUG - cout << "exception in MackieControlProtocol::set_state: " << e.what() << endl; -#endif + + if (node.property (X_("bank")) != 0) { + string bank = node.property (X_("bank"))->value(); + try { + set_active (true); + uint32_t new_bank = atoi (bank.c_str()); + if (_current_initial_bank != new_bank) { + switch_banks (new_bank); + } + } + catch (exception & e) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("exception in MackieControlProtocol::set_state: %1\n", e.what())); 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() ); - boost::shared_ptr route; - if ( control.group().is_strip() ) - { - if ( control.group().is_master() ) - { - 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; - } - - // 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 ) - { - 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 = master_route(); - if ( route ) - { - route->set_gain( slider_position_to_gain( state.pos ), this ); - port.write( builder.build_fader( (Fader&)control, state.pos ) ); - } - } - break; - - case Control::type_button: - if ( control.group().is_strip() ) - { - // strips - if ( route != 0 ) - { - handle_strip_button( control, state.button_state, route ); - } - else - { - // no route so always switch the light off - // because no signals will be emitted by a non-route - port.write( builder.build_led( control.led(), off ) ); - } - } - else if ( control.group().is_master() ) - { - // master fader touch - boost::shared_ptr route = master_route(); - if ( route ) - handle_strip_button( control, state.button_state, route ); - } - else - { - // handle all non-strip buttons - surface().handle_button( *this, state.button_state, dynamic_cast( control ) ); - } - break; - - // pot (jog wheel, external control) - case Control::type_pot: - if ( control.group().is_strip() ) - { - if ( route != 0 ) - { - if ( route->panner().size() == 1 ) - { - // assume pan for now - float xpos; - route->panner()[0]->get_effective_position (xpos); - - // calculate new value, and trim - xpos += state.delta; - if ( xpos > 1.0 ) - xpos = 1.0; - else if ( xpos < 0.0 ) - xpos = 0.0; - - route->panner()[0]->set_position( xpos ); - } - } - else - { - // it's a pot for an umnapped route, so turn all the lights off - port.write( builder.build_led_ring( dynamic_cast( control ), off ) ); - } - } - else - { - if ( control.name() == "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(); - } - - // doesn't work very well - session->request_locate( next, session->transport_rolling() ); - - // turn off the led ring, for bcf emulation mode - port.write( builder.build_led_ring( dynamic_cast( control ), off ) ); - } - else - { - cout << "external controller" << state.ticks << endl; - } - } - break; - - default: - cout << "Control::type not handled: " << control.type() << endl; - } -} ///////////////////////////////////////////////// // handlers for Route signals // TODO should these be part of RouteSignal? -// They started off as sigc handlers for signals +// They started off as signal/slot handlers for signals // from Route, but they're also used in polling for automation ///////////////////////////////////////////////// -void MackieControlProtocol::notify_solo_changed( RouteSignal * route_signal ) +// TODO handle plugin automation polling +string +MackieControlProtocol::format_bbt_timecode (framepos_t now_frame) { - try - { - Button & button = route_signal->strip().solo(); - route_signal->port().write( builder.build_led( button, route_signal->route().soloed() ) ); - } - catch( exception & e ) - { - cout << e.what() << endl; + Timecode::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 ARDOUR::Meter & meter = session->tempo_map().meter_at (now_frame); + int subdiv = 2; + if (meter.note_divisor() == 8 && (meter.divisions_per_bar() == 12.0 || meter.divisions_per_bar() == 9.0 || meter.divisions_per_bar() == 6.0)) { + subdiv = 3; } + + uint32_t subdivisions = bbt_time.ticks / uint32_t (Timecode::BBT_Time::ticks_per_beat / subdiv); + uint32_t ticks = bbt_time.ticks % uint32_t (Timecode::BBT_Time::ticks_per_beat / subdiv); + + os << setw(2) << setfill('0') << subdivisions + 1; + os << setw(3) << setfill('0') << ticks; + + return os.str(); } -void MackieControlProtocol::notify_mute_changed( RouteSignal * route_signal ) +string +MackieControlProtocol::format_timecode_timecode (framepos_t now_frame) { - try - { - Button & button = route_signal->strip().mute(); - route_signal->port().write( builder.build_led( button, route_signal->route().muted() ) ); - } - catch( exception & e ) - { - cout << e.what() << endl; - } + 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::notify_record_enable_changed( RouteSignal * route_signal ) +void +MackieControlProtocol::update_timecode_display() { - try - { - Button & button = route_signal->strip().recenable(); - route_signal->port().write( builder.build_led( button, route_signal->route().record_enabled() ) ); - } - catch( exception & e ) - { - cout << e.what() << endl; + boost::shared_ptr surface = surfaces.front(); + + if (surface->type() != mcu || !surface->has_timecode_display()) { + return; } -} -void MackieControlProtocol::notify_gain_changed( RouteSignal * route_signal ) -{ - try - { - Fader & fader = route_signal->strip().gain(); - if ( !fader.touch() ) - { - route_signal->port().write( builder.build_fader( fader, gain_to_slider_position( route_signal->route().effective_gain() ) ) ); - } + // do assignment here so current_frame is fixed + framepos_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: + return; } - catch( exception & e ) - { - cout << e.what() << endl; + + // 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 (timecode, _timecode_last); + _timecode_last = timecode; } } -void MackieControlProtocol::notify_name_changed( RouteSignal * route_signal ) +/////////////////////////////////////////// +// Session signals +/////////////////////////////////////////// + +void MackieControlProtocol::notify_parameter_changed (std::string const & p) { - try - { - // TODO implement MackieControlProtocol::notify_name_changed - } - catch( exception & e ) - { - cout << e.what() << endl; + 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 ("clicking", Config->get_clicking()); + } else { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("parameter changed: %1\n", p)); } } -// TODO deal with > 1 channel being panned -void MackieControlProtocol::notify_panner_changed( RouteSignal * route_signal ) +// RouteList is the set of routes that have just been added +void +MackieControlProtocol::notify_route_added (ARDOUR::RouteList & rl) { - try - { - Pot & pot = route_signal->strip().vpot(); - - if ( route_signal->route().panner().size() == 1 ) - { - float pos; - route_signal->route().panner()[0]->get_effective_position( pos); - route_signal->port().write( builder.build_led_ring( pot, ControlState( on, pos ) ) ); - } - else - { - route_signal->port().write( builder.zero_control( pot ) ); - } - } - catch( exception & e ) - { - cout << e.what() << endl; + // 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::RouteList ARS; + + for (ARS::iterator it = rl.begin(); it != rl.end(); ++it) { + (*it)->RemoteControlIDChanged.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_remote_id_changed, this), this); } } -// TODO handle plugin automation polling -void MackieControlProtocol::update_automation( RouteSignal & rs ) +void +MackieControlProtocol::notify_solo_active_changed (bool active) { - ARDOUR::AutoState gain_state = rs.route().gain_control()->list()->automation_state(); - if ( gain_state == Touch || gain_state == Play ) - { - notify_gain_changed( &rs ); - } + boost::shared_ptr surface = surfaces.front(); - ARDOUR::AutoState panner_state = rs.route().panner().automation_state(); - if ( panner_state == Touch || panner_state == Play ) - { - notify_panner_changed( &rs ); + Button * rude_solo = reinterpret_cast (surface->controls_by_name["solo"]); + + if (rude_solo) { + surface->write (builder.build_led (*rude_solo, active ? flashing : off)); } } -void MackieControlProtocol::poll_automation() +void +MackieControlProtocol::notify_remote_id_changed() { - if ( _active ) - { - // 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 ); + Sorted sorted = get_sorted_routes(); + uint32_t sz = n_strips(); + + // if a remote id has been moved off the end, we need to shift + // the current bank backwards. + + if (sorted.size() - _current_initial_bank < sz) { + // but don't shift backwards past the zeroth channel + switch_banks (max((Sorted::size_type) 0, sorted.size() - sz)); + } else { + // Otherwise just refresh the current bank + refresh_current_bank(); } } -///////////////////////////////////// -// Transport Buttons -///////////////////////////////////// +/////////////////////////////////////////// +// Transport signals +/////////////////////////////////////////// -LedState MackieControlProtocol::frm_left_press( Button & button ) +void +MackieControlProtocol::notify_loop_state_changed() { - // can use first_mark_before/after as well - Location * loc = session->locations()->first_location_before ( - session->transport_frame() - ); - if ( loc != 0 ) session->request_locate( loc->start(), session->transport_rolling() ); - return on; + update_global_button ("loop", session->get_play_loop()); } -LedState MackieControlProtocol::frm_left_release( Button & button ) +void +MackieControlProtocol::notify_transport_state_changed() { - return off; -} + // switch various play and stop buttons on / off + update_global_button ("play", session->transport_rolling()); + update_global_button ("stop", !session->transport_rolling()); + update_global_button ("rewind", session->transport_speed() < 0.0); + update_global_button ("ffwd", session->transport_speed() > 1.0); -LedState MackieControlProtocol::frm_right_press( Button & button ) -{ - // can use first_mark_before/after as well - Location * loc = session->locations()->first_location_after ( - session->transport_frame() - ); - if ( loc != 0 ) session->request_locate( loc->start(), session->transport_rolling() ); - return on; -} + _transport_previously_rolling = session->transport_rolling(); -LedState MackieControlProtocol::frm_right_release( Button & button ) -{ - return off; } -LedState MackieControlProtocol::stop_press( Button & button ) +void +MackieControlProtocol::notify_record_state_changed () { - session->request_stop(); - return on; -} + Button * rec = reinterpret_cast (surfaces.front()->controls_by_name["record"]); + if (rec) { + LedState ls; -LedState MackieControlProtocol::stop_release( Button & button ) -{ - return session->transport_stopped(); -} + switch (session->record_status()) { + case Session::Disabled: + ls = off; + break; + case Session::Recording: + ls = on; + break; + case Session::Enabled: + ls = flashing; + break; + } -LedState MackieControlProtocol::play_press( Button & button ) -{ - session->request_transport_speed( 1.0 ); - return on; + surfaces.front()->write (builder.build_led (*rec, ls)); + } } -LedState MackieControlProtocol::play_release( Button & button ) +list > +MackieControlProtocol::bundles () { - return session->transport_rolling(); + list > b; + b.push_back (_input_bundle); + b.push_back (_output_bundle); + return b; } -LedState MackieControlProtocol::record_press( Button & button ) +void +MackieControlProtocol::port_connected_or_disconnected (string a, string b, bool connected) { - if ( session->get_record_enabled() ) - session->disable_record( false ); - else - session->maybe_enable_record(); - return on; -} + /* If something is connected to one of our output ports, send MIDI to update the surface + to whatever state it should have. + */ -LedState MackieControlProtocol::record_release( Button & button ) -{ - if ( session->get_record_enabled() ) - { - if ( session->transport_rolling() ) - return on; - else - return flashing; + if (!connected) { + return; } - else - return off; -} -LedState MackieControlProtocol::rewind_press( Button & button ) -{ - session->request_transport_speed( -4.0 ); - return on; + for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) { + string const n = AudioEngine::instance()->make_port_name_non_relative ((*s)->port().output_port().name ()); + if (a == n || b == n) { + update_surfaces (); + return; + } + } } -LedState MackieControlProtocol::rewind_release( Button & button ) +void +MackieControlProtocol::do_request (MackieControlUIRequest* req) { - if ( _transport_previously_rolling ) - session->request_transport_speed( 1.0 ); - else - session->request_stop(); - return off; + if (req->type == CallSlot) { + + call_slot (MISSING_INVALIDATOR, req->the_slot); + + } else if (req->type == Quit) { + + stop (); + } } -LedState MackieControlProtocol::ffwd_press( Button & button ) +int +MackieControlProtocol::stop () { - session->request_transport_speed( 4.0 ); - return on; + BaseUI::quit (); + + return 0; } -LedState MackieControlProtocol::ffwd_release( Button & button ) +/** Add a timeout so that a control's in_use flag will be reset some time in the future. + * @param in_use_control the control whose in_use flag to reset. + * @param touch_control a touch control to emit an event for, or 0. + */ +void +MackieControlProtocol::add_in_use_timeout (Surface& surface, Control& in_use_control, Control* touch_control) { - if ( _transport_previously_rolling ) - session->request_transport_speed( 1.0 ); - else - session->request_stop(); - return off; -} + Glib::RefPtr timeout (Glib::TimeoutSource::create (250)); // milliseconds -/////////////////////////////////////////// -// Session signals -/////////////////////////////////////////// + in_use_control.in_use_connection.disconnect (); + in_use_control.in_use_connection = timeout->connect ( + sigc::bind (sigc::mem_fun (*this, &MackieControlProtocol::control_in_use_timeout), &surface, &in_use_control, touch_control)); + in_use_control.in_use_touch_control = touch_control; + + timeout->attach (main_loop()->get_context()); -void MackieControlProtocol::notify_parameter_changed( const char * name_str ) -{ - string name( name_str ); - if ( name == "punch-in" ) - { - update_global_button( "punch_in", Config->get_punch_in() ); - } - else if ( name == "punch-out" ) - { - update_global_button( "punch_out", Config->get_punch_out() ); - } - else if ( name == "clicking" ) - { - update_global_button( "clicking", Config->get_clicking() ); - } - else - { -#ifdef DEBUG - cout << "parameter changed: " << name << endl; -#endif - } -} + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("timeout queued for surface %1, control %2 touch control %3\n", + surface.number(), &in_use_control, touch_control));} -// RouteList is the set of routes that have just been added -void MackieControlProtocol::notify_route_added( ARDOUR::Session::RouteList & rl ) +/** Handle timeouts to reset in_use for controls that can't + * do this by themselves (e.g. pots, and faders without touch support). + * @param in_use_control the control whose in_use flag to reset. + * @param touch_control a touch control to emit an event for, or 0. + */ +bool +MackieControlProtocol::control_in_use_timeout (Surface* surface, Control* in_use_control, Control* touch_control) { - // currently assigned banks are less than the full set of - // strips, so activate the new strip now. - if ( route_signals.size() < route_table.size() ) - { - refresh_current_bank(); + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("timeout elapsed for surface %1, control %2 touch control %3\n", + surface->number(), in_use_control, touch_control)); + + in_use_control->set_in_use (false); + + if (touch_control) { + // empty control_state + ControlState control_state; + surface->handle_control_event (*touch_control, control_state); } - // 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; - for ( ARS::iterator it = rl.begin(); it != rl.end(); ++it ) - { - connections_back = (*it)->RemoteControlIDChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_remote_id_changed) ) ); - } + // only call this method once from the timer + return false; } -void MackieControlProtocol::notify_solo_active_changed( bool active ) +void +MackieControlProtocol::update_led (Surface& surface, Button& button, Mackie::LedState ls) { - Button * rude_solo = reinterpret_cast( surface().controls_by_name["solo"] ); - mcu_port().write( builder.build_led( *rude_solo, active ? flashing : off ) ); + if (ls != none) { + surface.port().write (builder.build_led (button, ls)); + } } -void MackieControlProtocol::notify_remote_id_changed() +void +MackieControlProtocol::handle_button_event (Surface& surface, Button& button, ButtonState bs) { - 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((Sorted::size_type) 0, sorted.size() - route_signals.size() ) ); - } - // Otherwise just refresh the current bank - else - { - refresh_current_bank(); + if (bs != press && bs != release) { + update_led (surface, button, none); + return; } -} + + LedState ls; -/////////////////////////////////////////// -// Transport signals -/////////////////////////////////////////// + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Handling %1 for button %2\n", (bs == press ? "press" : "release"), button.raw_id())); -void MackieControlProtocol::notify_record_state_changed() -{ - // switch rec button on / off / flashing - Button * rec = reinterpret_cast( surface().controls_by_name["record"] ); - mcu_port().write( builder.build_led( *rec, record_release( *rec ) ) ); -} + switch (button.raw_id()) { + case 0x28: // io + switch (bs) { + case press: ls = io_press (button); break; + case release: ls = io_release (button); break; + case neither: break; + } + break; + + case 0x29: // sends + switch (bs) { + case press: ls = sends_press (button); break; + case release: ls = sends_release (button); break; + case neither: break; + } + break; + + case 0x2a: // pan + switch (bs) { + case press: ls = pan_press (button); break; + case release: ls = pan_release (button); break; + case neither: break; + } + break; + + case 0x2b: // plugin + switch (bs) { + case press: ls = plugin_press (button); break; + case release: ls = plugin_release (button); break; + case neither: break; + } + break; + + case 0x2c: // eq + switch (bs) { + case press: ls = eq_press (button); break; + case release: ls = eq_release (button); break; + case neither: break; + } + break; + + case 0x2d: // dyn + switch (bs) { + case press: ls = dyn_press (button); break; + case release: ls = dyn_release (button); break; + case neither: break; + } + break; + + case 0x2e: // left + switch (bs) { + case press: ls = left_press (button); break; + case release: ls = left_release (button); break; + case neither: break; + } + break; + + case 0x2f: // right + switch (bs) { + case press: ls = right_press (button); break; + case release: ls = right_release (button); break; + case neither: break; + } + break; + + case 0x30: // channel_left + switch (bs) { + case press: ls = channel_left_press (button); break; + case release: ls = channel_left_release (button); break; + case neither: break; + } + break; + + case 0x31: // channel_right + switch (bs) { + case press: ls = channel_right_press (button); break; + case release: ls = channel_right_release (button); break; + case neither: break; + } + break; + + case 0x32: // flip + switch (bs) { + case press: ls = flip_press (button); break; + case release: ls = flip_release (button); break; + case neither: break; + } + break; -void MackieControlProtocol::notify_transport_state_changed() -{ - // switch various play and stop buttons on / off - 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( surface().controls_by_name["record"] ); - mcu_port().write( builder.build_led( *rec, record_release( *rec ) ) ); -} + case 0x33: // edit + switch (bs) { + case press: ls = edit_press (button); break; + case release: ls = edit_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::loop_press( Button & button ) -{ - session->request_play_loop( !session->get_play_loop() ); - return on; -} + case 0x34: // name_value + switch (bs) { + case press: ls = name_value_press (button); break; + case release: ls = name_value_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::loop_release( Button & button ) -{ - return session->get_play_loop(); -} + case 0x35: // timecode_beats + switch (bs) { + case press: ls = timecode_beats_press (button); break; + case release: ls = timecode_beats_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::punch_in_press( Button & button ) -{ - bool state = !Config->get_punch_in(); - Config->set_punch_in( state ); - return state; -} + case 0x36: // F1 + switch (bs) { + case press: ls = F1_press (button); break; + case release: ls = F1_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::punch_in_release( Button & button ) -{ - return Config->get_punch_in(); -} + case 0x37: // F2 + switch (bs) { + case press: ls = F2_press (button); break; + case release: ls = F2_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::punch_out_press( Button & button ) -{ - bool state = !Config->get_punch_out(); - Config->set_punch_out( state ); - return state; -} + case 0x38: // F3 + switch (bs) { + case press: ls = F3_press (button); break; + case release: ls = F3_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::punch_out_release( Button & button ) -{ - return Config->get_punch_out(); -} + case 0x39: // F4 + switch (bs) { + case press: ls = F4_press (button); break; + case release: ls = F4_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::home_press( Button & button ) -{ - session->goto_start(); - return on; -} + case 0x3a: // F5 + switch (bs) { + case press: ls = F5_press (button); break; + case release: ls = F5_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::home_release( Button & button ) -{ - return off; -} + case 0x3b: // F6 + switch (bs) { + case press: ls = F6_press (button); break; + case release: ls = F6_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::end_press( Button & button ) -{ - session->goto_end(); - return on; -} + case 0x3c: // F7 + switch (bs) { + case press: ls = F7_press (button); break; + case release: ls = F7_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::end_release( Button & button ) -{ - return off; -} + case 0x3d: // F8 + switch (bs) { + case press: ls = F8_press (button); break; + case release: ls = F8_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::clicking_press( Button & button ) -{ - bool state = !Config->get_clicking(); - Config->set_clicking( state ); - return state; -} + case 0x3e: // F9 + switch (bs) { + case press: ls = F9_press (button); break; + case release: ls = F9_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::clicking_release( Button & button ) -{ - return Config->get_clicking(); -} + case 0x3f: // F10 + switch (bs) { + case press: ls = F10_press (button); break; + case release: ls = F10_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::global_solo_press( Button & button ) -{ - bool state = !session->soloing(); - session->set_all_solo ( state ); - return state; -} + case 0x40: // F11 + switch (bs) { + case press: ls = F11_press (button); break; + case release: ls = F11_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::global_solo_release( Button & button ) -{ - return session->soloing(); -} + case 0x41: // F12 + switch (bs) { + case press: ls = F12_press (button); break; + case release: ls = F12_release (button); break; + case neither: break; + } + break; -///////////////////////////////////// -// Bank Switching -///////////////////////////////////// -LedState MackieControlProtocol::left_press( Button & button ) -{ - Sorted sorted = get_sorted_routes(); - if ( sorted.size() > route_table.size() ) - { - int new_initial = _current_initial_bank - route_table.size(); - if ( new_initial < 0 ) new_initial = 0; - if ( new_initial != int( _current_initial_bank ) ) - { - session->set_dirty(); - switch_banks( new_initial ); + case 0x42: // F13 + switch (bs) { + case press: ls = F13_press (button); break; + case release: ls = F13_release (button); break; + case neither: break; } - - return on; - } - else - { - return flashing; - } -} + break; -LedState MackieControlProtocol::left_release( Button & button ) -{ - return off; -} + case 0x43: // F14 + switch (bs) { + case press: ls = F14_press (button); break; + case release: ls = F14_release (button); break; + case neither: break; + } + break; -LedState MackieControlProtocol::right_press( Button & button ) -{ - Sorted sorted = get_sorted_routes(); - if ( sorted.size() > route_table.size() ) - { - uint32_t delta = sorted.size() - ( route_table.size() + _current_initial_bank ); - if ( delta > route_table.size() ) delta = route_table.size(); - if ( delta > 0 ) - { - session->set_dirty(); - switch_banks( _current_initial_bank + delta ); + case 0x44: // F15 + switch (bs) { + case press: ls = F15_press (button); break; + case release: ls = F15_release (button); break; + case neither: break; } - - return on; - } - else - { - return flashing; + break; + + case 0x45: // F16 + switch (bs) { + case press: ls = F16_press (button); break; + case release: ls = F16_release (button); break; + case neither: break; + } + break; + + case 0x46: // shift + switch (bs) { + case press: ls = shift_press (button); break; + case release: ls = shift_release (button); break; + case neither: break; + } + break; + + case 0x47: // option + switch (bs) { + case press: ls = option_press (button); break; + case release: ls = option_release (button); break; + case neither: break; + } + break; + + case Button::Ctrl: + switch (bs) { + case press: ls = control_press (button); break; + case release: ls = control_release (button); break; + case neither: break; + } + break; + + case 0x49: // cmd_alt + switch (bs) { + case press: ls = cmd_alt_press (button); break; + case release: ls = cmd_alt_release (button); break; + case neither: break; + } + break; + + case 0x4a: // on + switch (bs) { + case press: ls = on_press (button); break; + case release: ls = on_release (button); break; + case neither: break; + } + break; + + case 0x4b: // rec_ready + switch (bs) { + case press: ls = rec_ready_press (button); break; + case release: ls = rec_ready_release (button); break; + case neither: break; + } + break; + + case Button::Undo: // undo + switch (bs) { + case press: ls = undo_press (button); break; + case release: ls = undo_release (button); break; + case neither: break; + } + break; + + case Button::Save: + switch (bs) { + case press: ls = save_press (button); break; + case release: ls = save_release (button); break; + case neither: break; + } + break; + + case Button::Touch: // touch + switch (bs) { + case press: ls = touch_press (button); break; + case release: ls = touch_release (button); break; + case neither: break; + } + break; + + case Button::Redo: // redo + switch (bs) { + case press: ls = redo_press (button); break; + case release: ls = redo_release (button); break; + case neither: break; + } + break; + + case Button::Marker: // marker + switch (bs) { + case press: ls = marker_press (button); break; + case release: ls = marker_release (button); break; + case neither: break; + } + break; + + case Button::Enter: // enter + switch (bs) { + case press: ls = enter_press (button); break; + case release: ls = enter_release (button); break; + case neither: break; + } + break; + + case 0x52: // cancel + switch (bs) { + case press: ls = cancel_press (button); break; + case release: ls = cancel_release (button); break; + case neither: break; + } + break; + + case 0x53: // mixer + switch (bs) { + case press: ls = mixer_press (button); break; + case release: ls = mixer_release (button); break; + case neither: break; + } + break; + + case 0x54: // frm_left + switch (bs) { + case press: ls = frm_left_press (button); break; + case release: ls = frm_left_release (button); break; + case neither: break; + } + break; + + case 0x55: // frm_right + switch (bs) { + case press: ls = frm_right_press (button); break; + case release: ls = frm_right_release (button); break; + case neither: break; + } + break; + + case 0x56: // loop + switch (bs) { + case press: ls = loop_press (button); break; + case release: ls = loop_release (button); break; + case neither: break; + } + break; + + case 0x57: // punch_in + switch (bs) { + case press: ls = punch_in_press (button); break; + case release: ls = punch_in_release (button); break; + case neither: break; + } + break; + + case 0x58: // punch_out + switch (bs) { + case press: ls = punch_out_press (button); break; + case release: ls = punch_out_release (button); break; + case neither: break; + } + break; + + case 0x59: // home + switch (bs) { + case press: ls = home_press (button); break; + case release: ls = home_release (button); break; + case neither: break; + } + break; + + case 0x5a: // end + switch (bs) { + case press: ls = end_press (button); break; + case release: ls = end_release (button); break; + case neither: break; + } + break; + + case Button::Rewind: + switch (bs) { + case press: ls = rewind_press (button); break; + case release: ls = rewind_release (button); break; + case neither: break; + } + break; + + case Button::Ffwd: + switch (bs) { + case press: ls = ffwd_press (button); break; + case release: ls = ffwd_release (button); break; + case neither: break; + } + break; + + case Button::Stop: + switch (bs) { + case press: ls = stop_press (button); break; + case release: ls = stop_release (button); break; + case neither: break; + } + break; + + case Button::Play: + switch (bs) { + case press: ls = play_press (button); break; + case release: ls = play_release (button); break; + case neither: break; + } + break; + + case 0x5f: // record + switch (bs) { + case press: ls = record_press (button); break; + case release: ls = record_release (button); break; + case neither: break; + } + break; + + case 0x60: // cursor_up + switch (bs) { + case press: ls = cursor_up_press (button); break; + case release: ls = cursor_up_release (button); break; + case neither: break; + } + break; + + case 0x61: // cursor_down + switch (bs) { + case press: ls = cursor_down_press (button); break; + case release: ls = cursor_down_release (button); break; + case neither: break; + } + break; + + case 0x62: // cursor_left + switch (bs) { + case press: ls = cursor_left_press (button); break; + case release: ls = cursor_left_release (button); break; + case neither: break; + } + break; + + case 0x63: // cursor_right + switch (bs) { + case press: ls = cursor_right_press (button); break; + case release: ls = cursor_right_release (button); break; + case neither: break; + } + break; + + case 0x64: // zoom + switch (bs) { + case press: ls = zoom_press (button); break; + case release: ls = zoom_release (button); break; + case neither: break; + } + break; + + case 0x65: // scrub + switch (bs) { + case press: ls = scrub_press (button); break; + case release: ls = scrub_release (button); break; + case neither: break; + } + break; + + case 0x66: // user_a + switch (bs) { + case press: ls = user_a_press (button); break; + case release: ls = user_a_release (button); break; + case neither: break; + } + break; + + case 0x67: // user_b + switch (bs) { + case press: ls = user_b_press (button); break; + case release: ls = user_b_release (button); break; + case neither: break; + } + break; + } -} -LedState MackieControlProtocol::right_release( Button & button ) -{ - return off; + update_led (surface, button, ls); } -LedState MackieControlProtocol::channel_left_press( Button & button ) +void +MackieControlProtocol::select_track (boost::shared_ptr r) { - Sorted sorted = get_sorted_routes(); - if ( sorted.size() > route_table.size() ) - { - prev_track(); - return on; - } - else - { - return flashing; + if (_modifier_state == MODIFIER_SHIFT) { + r->gain_control()->set_value (0.0); + } else { + if (_current_selected_track > 0 && r->remote_control_id() == (uint32_t) _current_selected_track) { + UnselectTrack (); /* EMIT SIGNAL */ + _current_selected_track = -1; + } else { + SelectByRID (r->remote_control_id()); /* EMIT SIGNAL */ + _current_selected_track = r->remote_control_id();; + } } } -LedState MackieControlProtocol::channel_left_release( Button & button ) +bool +MackieControlProtocol::midi_input_handler (IOCondition ioc, MIDI::Port* port) { - return off; -} + DEBUG_TRACE (DEBUG::MidiIO, string_compose ("something happend on %1\n", port->name())); -LedState MackieControlProtocol::channel_right_press( Button & button ) -{ - Sorted sorted = get_sorted_routes(); - if ( sorted.size() > route_table.size() ) - { - next_track(); - return on; + if (ioc & ~IO_IN) { + return false; } - else - { - return flashing; + + if (ioc & IO_IN) { + + CrossThreadChannel::drain (port->selectable()); + + DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", port->name())); + framepos_t now = session->engine().frame_time(); + port->parse (now); } -} -LedState MackieControlProtocol::channel_right_release( Button & button ) -{ - return off; + return true; } -///////////////////////////////////// -// Functions -///////////////////////////////////// -LedState MackieControlProtocol::marker_press( Button & button ) +void +MackieControlProtocol::clear_ports () { - // 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(*(session->locations()), &before, &after)); - session->commit_reversible_command (); - return on; -} + for (PortSources::iterator i = port_sources.begin(); i != port_sources.end(); ++i) { + g_source_destroy (*i); + g_source_unref (*i); + } -LedState MackieControlProtocol::marker_release( Button & button ) -{ - return off; + port_sources.clear (); } +