MCP: add debug tracing for timeouts
[ardour.git] / libs / surfaces / mackie / mackie_port.cc
index d8e7d17624ec7af54d4f51311f51851aacd6b49c..f725407b021e609ba1273677a6f68585da9735db 100644 (file)
 #include "controls.h"
 #include "surface.h"
 
-#include <midi++/types.h>
-#include <midi++/port.h>
-#include <midi++/alsa_sequencer.h>
-#include <sigc++/sigc++.h>
+#include <glibmm/main.h>
+
 #include <boost/shared_array.hpp>
-#include <ardour/configuration.h>
+
+#include "midi++/types.h"
+#include "midi++/port.h"
+
+#include "ardour/debug.h"
+#include "ardour/rc_configuration.h"
 
 #include "i18n.h"
 
 
 using namespace std;
 using namespace Mackie;
+using namespace ARDOUR;
+using namespace PBD;
 
 // The MCU sysex header
-MidiByteArray mackie_sysex_hdr ( 5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10 );
+MidiByteArray mackie_sysex_hdr  (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10);
 
 // The MCU extender sysex header
-MidiByteArray mackie_sysex_hdr_xt ( 5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11 );
-
-MackiePort::MackiePort( MackieControlProtocol & mcp, MIDI::Port & port, int number, port_type_t port_type )
-: SurfacePort( port, number )
-, _mcp( mcp )
-, _port_type( port_type )
-, _emulation( none )
-, _initialising( true )
+MidiByteArray mackie_sysex_hdr_xt  (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11);
+
+MackiePort::MackiePort (MackieControlProtocol & mcp, MIDI::Port & input_port, MIDI::Port & output_port, int number, port_type_t port_type)
+       : SurfacePort (input_port, output_port, number)
+       , _mcp (mcp)
+       , _port_type (port_type)
+       , _emulation (none)
+       , _initialising (true)
+       , _connected (false)
 {
-       cout << "MackiePort::MackiePort" <<endl;
+       DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::MackiePort\n");
 }
 
 MackiePort::~MackiePort()
 {
+       DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::~MackiePort\n");
        close();
+       DEBUG_TRACE (DEBUG::MackieControl, "~MackiePort finished\n");
 }
 
 int MackiePort::strips() const
 {
-       if ( _port_type == mcu )
+       if  (_port_type == mcu)
        {
-               switch ( _emulation )
+               switch  (_emulation)
                {
                        // BCF2000 only has 8 faders, so reserve one for master
                        case bcf2000: return 7;
                        case mackie: return 8;
                        case none:
                        default:
-                               throw MackieControlException( "MackiePort::strips: don't know what emulation we're using" );
+                               throw MackieControlException ("MackiePort::strips: don't know what emulation we're using");
                }
        }
        else
@@ -82,25 +90,30 @@ int MackiePort::strips() const
 // should really be in MackiePort
 void MackiePort::open()
 {
-       cout << "MackiePort::open " << *this << endl;
-       _sysex = port().input()->sysex.connect( ( mem_fun (*this, &MackiePort::handle_midi_sysex) ) );
+       DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackiePort::open %1\n", *this));
        
+       input_port().parser()->sysex.connect_same_thread (sysex_connection, boost::bind (&MackiePort::handle_midi_sysex, this, _1, _2, _3));
+                    
        // make sure the device is connected
        init();
 }
 
 void MackiePort::close()
 {
-       // disconnect signals
-       _any.disconnect();
-       _sysex.disconnect();
+       DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::close\n");
        
-       // or emit a "closing" signal
+       // disconnect signals
+
+       sysex_connection.disconnect();
+       ScopedConnectionList::drop_connections ();
+       _connected = false;
+
+       // TODO emit a "closing" signal?
 }
 
 const MidiByteArray & MackiePort::sysex_hdr() const
 {
-       switch ( _port_type )
+       switch  (_port_type)
        {
                case mcu: return mackie_sysex_hdr;
                case ext: return mackie_sysex_hdr_xt;
@@ -109,276 +122,279 @@ const MidiByteArray & MackiePort::sysex_hdr() const
        return mackie_sysex_hdr;
 }
 
-Control & MackiePort::lookup_control( const MidiByteArray & bytes )
-{
-       Control * control = 0;
-       int midi_id = -1;
-       MIDI::byte midi_type = bytes[0] & 0xf0; //0b11110000
-       switch( midi_type )
-       {
-               // fader
-               case MackieMidiBuilder::midi_fader_id:
-                       midi_id = bytes[0] & 0x0f;
-                       control = _mcp.surface().faders[midi_id];
-                       if ( control == 0 )
-                       {
-                               ostringstream os;
-                               os << "control for fader" << midi_id << " is null";
-                               throw MackieControlException( os.str() );
-                       }
-                       break;
-                       
-               // button
-               case MackieMidiBuilder::midi_button_id:
-                       midi_id = bytes[1];
-                       control = _mcp.surface().buttons[midi_id];
-                       if ( control == 0 )
-                       {
-                               ostringstream os;
-                               os << "control for button" << midi_id << " is null";
-                               throw MackieControlException( os.str() );
-                       }
-                       break;
-                       
-               // pot (jog wheel, external control)
-               case MackieMidiBuilder::midi_pot_id:
-                       midi_id = bytes[1] & 0x1f;
-                       control = _mcp.surface().pots[midi_id];
-                       if ( control == 0 )
-                       {
-                               ostringstream os;
-                               os << "control for button" << midi_id << " is null";
-                               throw MackieControlException( os.str() );
-                       }
-                       break;
-               
-               default:
-                       ostringstream os;
-                       os << "Cannot find control for " << bytes;
-                       throw MackieControlException( os.str() );
-       }
-       return *control;
-}
-
-MidiByteArray calculate_challenge_response( MidiByteArray::iterator begin, MidiByteArray::iterator end )
+MidiByteArray calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end)
 {
        MidiByteArray l;
-       back_insert_iterator<MidiByteArray> back ( l );
-       copy( begin, end, back );
+       back_insert_iterator<MidiByteArray> back  (l);
+       copy (begin, end, back);
        
        MidiByteArray retval;
        
        // this is how to calculate the response to the challenge.
        // from the Logic docs.
-       retval << ( 0x7f & ( l[0] + ( l[1] ^ 0xa ) - l[3] ) );
-       retval << ( 0x7f & ( ( l[2] >> l[3] ) ^ ( l[0] + l[3] ) ) );
-       retval << ( 0x7f & ( l[3] - ( l[2] << 2 ) ^ ( l[0] | l[1] ) ) );
-       retval << ( 0x7f & ( l[1] - l[2] + ( 0xf0 ^ ( l[3] << 4 ) ) ) );
+       retval <<  (0x7f &  (l[0] +  (l[1] ^ 0xa) - l[3]));
+       retval <<  (0x7f &  ( (l[2] >> l[3]) ^  (l[0] + l[3])));
+       retval <<  (0x7f &  ((l[3] -  (l[2] << 2)) ^  (l[0] | l[1])));
+       retval <<  (0x7f &  (l[1] - l[2] +  (0xf0 ^  (l[3] << 4))));
        
        return retval;
 }
 
 // not used right now
-MidiByteArray MackiePort::host_connection_query( MidiByteArray & bytes )
+MidiByteArray MackiePort::host_connection_query (MidiByteArray & bytes)
 {
+       MidiByteArray response;
+
        // handle host connection query
-       cout << "host connection query: " << bytes << endl;
+       DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host connection query: %1\n", bytes));
        
-       if ( bytes.size() != 18 )
-       {
-               finalise_init( false );
-               ostringstream os;
-               os << "expecting 18 bytes, read " << bytes << " from " << port().name();
-               throw MackieControlException( os.str() );
+       if  (bytes.size() != 18) {
+               finalise_init (false);
+               cerr << "expecting 18 bytes, read " << bytes << " from " << input_port().name() << endl;
+               return response;
        }
 
        // build and send host connection reply
-       MidiByteArray response;
        response << 0x02;
-       copy( bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter( response ) );
-       response << calculate_challenge_response( bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4 );
+       copy (bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter (response));
+       response << calculate_challenge_response (bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4);
        return response;
 }
 
 // not used right now
-MidiByteArray MackiePort::host_connection_confirmation( const MidiByteArray & bytes )
+MidiByteArray MackiePort::host_connection_confirmation (const MidiByteArray & bytes)
 {
-       cout << "host_connection_confirmation: " << bytes << endl;
+       DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host_connection_confirmation: %1\n", bytes));
        
        // decode host connection confirmation
-       if ( bytes.size() != 14 )
-       {
-               finalise_init( false );
+       if  (bytes.size() != 14) {
+               finalise_init (false);
                ostringstream os;
-               os << "expecting 14 bytes, read " << bytes << " from " << port().name();
-               throw MackieControlException( os.str() );
+               os << "expecting 14 bytes, read " << bytes << " from " << input_port().name();
+               throw MackieControlException (os.str());
        }
        
        // send version request
-       return MidiByteArray( 2, 0x13, 0x00 );
+       return MidiByteArray (2, 0x13, 0x00);
 }
 
-void MackiePort::probe_emulation( const MidiByteArray & bytes )
+void MackiePort::probe_emulation (const MidiByteArray &)
 {
+#if 0
        cout << "MackiePort::probe_emulation: " << bytes.size() << ", " << bytes << endl;
-       string version_string;
-       for ( int i = 6; i < 11; ++i ) version_string.append( 1, (char)bytes[i] );
+
+       MidiByteArray version_string;
+       for  (int i = 6; i < 11; ++i) version_string << bytes[i];
        cout << "version_string: " << version_string << endl;
+#endif
        
        // TODO investigate using serial number. Also, possibly size of bytes might
        // give an indication. Also, apparently MCU sends non-documented messages
        // sometimes.
        if (!_initialising)
        {
-               cout << "MackiePort::probe_emulation out of sequence." << endl;
+               //cout << "MackiePort::probe_emulation out of sequence." << endl;
                return;
        }
 
-       // probing doesn't work very well, so just use a config variable
-       // to set the emulation mode
-       bool emulation_ok = false;
-       if ( ARDOUR::Config->get_mackie_emulation() == "bcf" )
-       {
-               _emulation = bcf2000;
-               emulation_ok = true;
-       }
-       else if ( ARDOUR::Config->get_mackie_emulation() == "mcu" )
-       {
-               _emulation = mackie;
-               emulation_ok = true;
-       }
-       else
-       {
-               cout << "unknown mackie emulation: " << ARDOUR::Config->get_mackie_emulation() << endl;
-               emulation_ok = false;
-       }
-       
-       finalise_init( emulation_ok );
+       finalise_init (true);
 }
 
 void MackiePort::init()
 {
-       cout << "MackiePort::init" << endl;
+       DEBUG_TRACE (DEBUG::MackieControl,  "MackiePort::init\n");
+
        init_mutex.lock();
        _initialising = true;
-       
-       cout << "MackiePort::lock acquired" << endl;
+
+       DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::init lock acquired\n");
+
        // emit pre-init signal
        init_event();
        
        // kick off initialisation. See docs in header file for init()
-       write_sysex ( MidiByteArray (2, 0x13, 0x00 ));
+       
+       // bypass the init sequence because sometimes the first
+       // message doesn't get to the unit, and there's no way
+       // to do a timed lock in Glib.
+       //write_sysex  (MidiByteArray  (2, 0x13, 0x00));
+       
+       finalise_init (true);
 }
 
-void MackiePort::finalise_init( bool yn )
+void MackiePort::finalise_init (bool yn)
 {
-       cout << "MackiePort::finalise_init" << endl;
+       DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::finalise_init\n");
+
+       bool emulation_ok = false;
        
-       SurfacePort::active( yn );
+       // probing doesn't work very well, so just use a config variable
+       // to set the emulation mode
+       // TODO This might have to be specified on a per-port basis
+       // in the config file
+       // if an mcu and a bcf are needed to work as one surface
+       if  (_emulation == none) {
 
-       if ( yn )
-       {
+               // TODO same as code in mackie_control_protocol.cc
+               if  (ARDOUR::Config->get_mackie_emulation() == "bcf") {
+                       _emulation = bcf2000;
+                       emulation_ok = true;
+               } else if  (ARDOUR::Config->get_mackie_emulation() == "mcu")  {
+                       _emulation = mackie;
+                       emulation_ok = true;
+               } else {
+                       cout << "unknown mackie emulation: " << ARDOUR::Config->get_mackie_emulation() << endl;
+                       emulation_ok = false;
+               }
+       }
+       
+       yn = yn && emulation_ok;
+       
+       SurfacePort::active (yn);
+
+       if (yn) {
                active_event();
                
                // start handling messages from controls
-               _any = port().input()->any.connect( ( mem_fun (*this, &MackiePort::handle_midi_any) ) );
+               connect_to_signals ();
        }
+
        _initialising = false;
        init_cond.signal();
        init_mutex.unlock();
+
+       DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::finalise_init lock released\n");
+}
+
+void MackiePort::connect_to_signals ()
+{
+       if (!_connected) {
+
+               MIDI::Parser* p = input_port().parser();
+
+               p->controller.connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_controller_message, this, _1, _2));
+               
+               p->channel_pitchbend[0].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 0U));
+               p->channel_pitchbend[1].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 1U));
+               p->channel_pitchbend[2].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 2U));
+               p->channel_pitchbend[3].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 3U));
+               p->channel_pitchbend[4].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 4U));
+               p->channel_pitchbend[5].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 5U));
+               p->channel_pitchbend[6].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 6U));
+               p->channel_pitchbend[7].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 7U));
+               
+               _connected = true;
+       }
 }
 
 bool MackiePort::wait_for_init()
 {
-       Glib::Mutex::Lock lock( init_mutex );
-       while ( _initialising )
-       {
-               cout << "MackiePort::wait_for_active waiting" << endl;
-               init_cond.wait( init_mutex );
-               cout << "MackiePort::wait_for_active released" << endl;
+       Glib::Mutex::Lock lock (init_mutex);
+       while (_initialising) {
+               DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active waiting\n");
+               init_cond.wait (init_mutex);
+               DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active released\n");
        }
-       cout << "MackiePort::wait_for_active returning" << endl;
+       DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active returning\n");
        return SurfacePort::active();
 }
 
-void MackiePort::handle_midi_sysex (MIDI::Parser & parser, MIDI::byte * raw_bytes, size_t count )
+void MackiePort::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count)
 {
-       MidiByteArray bytes( count, raw_bytes );
-       cout << "handle_midi_sysex: " << bytes << endl;
-       switch( bytes[5] )
+       MidiByteArray bytes (count, raw_bytes);
+
+       DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi_sysex: %1\n", bytes));
+
+       switch (bytes[5])
        {
                case 0x01:
-                       // not used right now
-                       write_sysex( host_connection_query( bytes ) );
+                       write_sysex (host_connection_query (bytes));
                        break;
                case 0x03:
                        // not used right now
-                       write_sysex( host_connection_confirmation( bytes ) );
+                       write_sysex (host_connection_confirmation (bytes));
                        break;
                case 0x04:
-                       inactive_event();
+                       inactive_event ();
                        cout << "host connection error" << bytes << endl;
                        break;
                case 0x14:
-                       probe_emulation( bytes );
+                       probe_emulation (bytes);
                        break;
                default:
                        cout << "unknown sysex: " << bytes << endl;
        }
 }
 
-// converts midi messages into control_event signals
-void MackiePort::handle_midi_any (MIDI::Parser & parser, MIDI::byte * raw_bytes, size_t count )
+void
+MackiePort::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uint32_t fader_id)
 {
-       MidiByteArray bytes( count, raw_bytes );
-       try
-       {
-               // ignore sysex messages
-               if ( bytes[0] == MIDI::sysex ) return;
+       Control* control = _mcp.surface().faders[fader_id];
 
-               Control & control = lookup_control( bytes );
+       if (control) {
+               // only the top-order 10 bits out of 14 are used
+               int midi_pos = pb & 0x3ff;
+       
+               // in_use is set by the MackieControlProtocol::handle_strip_button
                
-               // This handles incoming bytes. Outgoing bytes
-               // are sent by the signal handlers.
-               switch ( control.type() )
-               {
-                       // fader
-                       case Control::type_fader:
-                               {
-                                       // for a BCF2000, max is 7f for high-order byte and 0x70 for low-order byte
-                                       // According to the Logic docs, these should both be 0x7f.
-                                       // Although it does mention something about only the top-order
-                                       // 10 bits out of 14 being used
-                                       int midi_pos = ( bytes[2] << 7 ) + bytes[1];
-                                       control_event( *this, control, float(midi_pos) / float(0x3fff) );
-                               }
-                               break;
-                               
-                       // button
-                       case Control::type_button:
-                               control_event( *this, control, bytes[2] == 0x7f ? press : release );
-                               break;
-                               
-                       // pot (jog wheel, external control)
-                       case Control::type_pot:
-                               {
-                                       ControlState state;
-                                       
-                                       // bytes[2] & 0b01000000 (0x40) give sign
-                                       int sign = ( bytes[2] & 0x40 ) == 0 ? 1 : -1; 
-                                       // bytes[2] & 0b00111111 (0x3f) gives delta
-                                       state.ticks = ( bytes[2] & 0x3f) * sign;
-                                       state.delta = float( state.ticks ) / float( 0x3f );
-                                       
-                                       control_event( *this, control, state );
-                               }
-                               break;
-                       default:
-                               cerr << "Do not understand control type " << control;
-               }
+               // relies on implicit ControlState constructor
+               _mcp.handle_control_event (*this, *control, float (midi_pos) / float(0x3ff));
        }
-       catch( MackieControlException & e )
-       {
-               cout << bytes << ' ' << e.what() << endl;
+}
+
+void 
+MackiePort::handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes* ev)
+{
+       DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackiePort::handle_midi_controller %1 = %2\n", ev->controller_number, ev->value));
+
+       Control* control;
+
+       switch (ev->controller_number & 0xf0) {
+       case Control::type_button:
+               control = _mcp.surface().buttons[ev->controller_number];
+               if (control) {
+                       control->set_in_use (true);
+                       ControlState control_state (ev->value == 0x7f ? press : release);
+                       control->set_in_use (control_state.button_state == press);
+                       control_event (*this, *control, control_state);
+               }
+               
+               break;
+
+       case Control::type_pot:
+               control = _mcp.surface().pots[ev->controller_number];
+               if (control) {
+                       ControlState state;
+                       
+                       // bytes[2] & 0b01000000 (0x40) give sign
+                       state.sign = (ev->value & 0x40) == 0 ? 1 : -1; 
+                       // bytes[2] & 0b00111111 (0x3f) gives delta
+                       state.ticks = (ev->value & 0x3f);
+                       if (state.ticks == 0) {
+                               /* euphonix and perhaps other devices send zero
+                                  when they mean 1, we think.
+                               */
+                               state.ticks = 1;
+                       }
+                       state.delta = float (state.ticks) / float (0x3f);
+                       
+                       /* Pots only emit events when they move, not when they
+                          stop moving. So to get a stop event, we need to use a timeout.
+                       */
+                       
+                       control->set_in_use (true);
+                       _mcp.add_in_use_timeout (*this, *control, control);
+                       control_event (*this, *control, state);
+               }
+               break;
+       
+       default:
+               break;
        }
 }
+
+void
+MackiePort::control_event (SurfacePort& sp, Control& c, const ControlState& cs)
+{
+       _mcp.handle_control_event (sp, c, cs);
+}