#include "controls.h"
#include "surface.h"
-#include <midi++/types.h>
-#include <midi++/port.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 );
// 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 )
+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 )
{
- //cout << "MackiePort::MackiePort" <<endl;
+ DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::MackiePort\n");
}
MackiePort::~MackiePort()
{
- //cout << "~MackiePort" << endl;
+ DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::~MackiePort\n");
close();
- //cout << "~MackiePort finished" << endl;
+ DEBUG_TRACE (DEBUG::MackieControl, "~MackiePort finished\n");
}
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()
{
- //cout << "MackiePort::close" << endl;
+ DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::close\n");
// disconnect signals
- _any.disconnect();
- _sysex.disconnect();
+ any_connection.disconnect();
+ sysex_connection.disconnect();
// TODO emit a "closing" signal?
- //cout << "MackiePort::close finished" << endl;
}
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 l;
// 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[3] - ( l[2] << 2 )) ^ ( l[0] | l[1] ) ) );
retval << ( 0x7f & ( l[1] - l[2] + ( 0xf0 ^ ( l[3] << 4 ) ) ) );
return retval;
MidiByteArray MackiePort::host_connection_query( MidiByteArray & bytes )
{
// 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();
+ os << "expecting 18 bytes, read " << bytes << " from " << input_port().name();
throw MackieControlException( os.str() );
}
// not used right now
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 );
ostringstream os;
- os << "expecting 14 bytes, read " << bytes << " from " << port().name();
+ os << "expecting 14 bytes, read " << bytes << " from " << input_port().name();
throw MackieControlException( os.str() );
}
return MidiByteArray( 2, 0x13, 0x00 );
}
-void MackiePort::probe_emulation( const MidiByteArray & bytes )
+void MackiePort::probe_emulation (const MidiByteArray &)
{
- //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] );
- //cout << "version_string: " << version_string << endl;
+#if 0
+ cout << "MackiePort::probe_emulation: " << bytes.size() << ", " << bytes << endl;
+
+ 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;
}
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();
void MackiePort::finalise_init( bool yn )
{
- //cout << "MackiePort::finalise_init" << endl;
+ DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::finalise_init\n");
+
bool emulation_ok = false;
// 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 )
{
+ // TODO same as code in mackie_control_protocol.cc
if ( ARDOUR::Config->get_mackie_emulation() == "bcf" )
{
_emulation = bcf2000;
SurfacePort::active( yn );
- if ( yn )
- {
+ if (yn) {
active_event();
// start handling messages from controls
- _any = port().input()->any.connect( ( mem_fun (*this, &MackiePort::handle_midi_any) ) );
+ connect_any();
}
+
_initialising = false;
init_cond.signal();
init_mutex.unlock();
+
+ DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::finalise_init lock released\n");
+}
+
+void MackiePort::connect_any()
+{
+ if (!any_connection.connected()) {
+ input_port().parser()->any.connect_same_thread (any_connection, boost::bind (&MackiePort::handle_midi_any, this, _1, _2, _3));
+ }
}
bool MackiePort::wait_for_init()
{
Glib::Mutex::Lock lock( init_mutex );
- while ( _initialising )
- {
- //cout << "MackiePort::wait_for_active waiting" << endl;
+ while (_initialising) {
+ DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active waiting\n");
init_cond.wait( init_mutex );
- //cout << "MackiePort::wait_for_active released" << endl;
+ 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;
+ 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;
}
}
+Control & MackiePort::lookup_control( MIDI::byte * bytes, size_t count )
+{
+ // Don't instantiate a MidiByteArray here unless it's needed for exceptions.
+ // Reason being that this method is called for every single incoming
+ // midi event, and it needs to be as efficient as possible.
+
+ Control * control = 0;
+ MIDI::byte midi_type = bytes[0] & 0xf0; //0b11110000
+
+ switch (midi_type) {
+ // fader
+ case MackieMidiBuilder::midi_fader_id:
+ {
+ int midi_id = bytes[0] & 0x0f;
+ control = _mcp.surface().faders[midi_id];
+ if ( control == 0 )
+ {
+ MidiByteArray mba( count, bytes );
+ ostringstream os;
+ os << "Control for fader" << bytes << " id " << midi_id << " is null";
+ throw MackieControlException( os.str() );
+ }
+ break;
+ }
+
+ // button
+ case MackieMidiBuilder::midi_button_id:
+ control = _mcp.surface().buttons[bytes[1]];
+ if ( control == 0 )
+ {
+ MidiByteArray mba( count, bytes );
+ ostringstream os;
+ os << "Control for button " << mba << " is null";
+ throw MackieControlException( os.str() );
+ }
+ break;
+
+ // pot (jog wheel, external control)
+ case MackieMidiBuilder::midi_pot_id:
+ control = _mcp.surface().pots[bytes[1]];
+ if ( control == 0 )
+ {
+ MidiByteArray mba( count, bytes );
+ ostringstream os;
+ os << "Control for rotary " << mba << " is null";
+ throw MackieControlException( os.str() );
+ }
+ break;
+
+ default:
+ MidiByteArray mba( count, bytes );
+ ostringstream os;
+ os << "Cannot find control for " << mba;
+ throw MackieControlException( os.str() );
+ }
+ return *control;
+}
+
+bool MackiePort::handle_control_timeout_event ( Control * control )
+{
+ // empty control_state
+ ControlState control_state;
+ control->in_use( false );
+ control_event( *this, *control, control_state );
+
+ // only call this method once from the timer
+ return false;
+}
+
// converts midi messages into control_event signals
-void MackiePort::handle_midi_any (MIDI::Parser & parser, MIDI::byte * raw_bytes, size_t count )
+// it might be worth combining this with lookup_control
+// because they have similar logic flows.
+void MackiePort::handle_midi_any (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count )
{
MidiByteArray bytes( count, raw_bytes );
+ DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackiePort::handle_midi_any %1\n", bytes));
+
try
{
// ignore sysex messages
- if ( bytes[0] == MIDI::sysex ) return;
+ if ( raw_bytes[0] == MIDI::sysex ) return;
- Control & control = lookup_control( bytes );
+ // sanity checking
+ if (count != 3) {
+ ostringstream os;
+ MidiByteArray mba( count, raw_bytes );
+ os << "MackiePort::handle_midi_any needs 3 bytes, but received " << mba;
+ throw MackieControlException( os.str() );
+ }
+
+ Control & control = lookup_control( raw_bytes, count );
+ control.in_use( true );
// This handles incoming bytes. Outgoing bytes
// are sent by the signal handlers.
- switch ( control.type() )
- {
+ 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;
+ {
+ // only the top-order 10 bits out of 14 are used
+ int midi_pos = ( ( raw_bytes[2] << 7 ) + raw_bytes[1] ) >> 4;
+
+ // in_use is set by the MackieControlProtocol::handle_strip_button
+
+ // relies on implicit ControlState constructor
+ control_event( *this, control, float(midi_pos) / float(0x3ff) );
+ }
+ break;
// button
case Control::type_button:
- control_event( *this, control, bytes[2] == 0x7f ? press : release );
+ {
+ ControlState control_state( raw_bytes[2] == 0x7f ? press : release );
+ control.in_use( control_state.button_state == press );
+ control_event( *this, control, control_state );
+
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 );
- }
+ {
+ ControlState state;
+
+ // bytes[2] & 0b01000000 (0x40) give sign
+ state.sign = ( raw_bytes[2] & 0x40 ) == 0 ? 1 : -1;
+ // bytes[2] & 0b00111111 (0x3f) gives delta
+ state.ticks = ( raw_bytes[2] & 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.
+ */
+ // this is set to false ...
+ control.in_use( true );
+
+ // ... by this timeout
+
+ // first disconnect any previous timeouts
+ control.in_use_connection.disconnect();
+
+ // now connect a new timeout to call handle_control_timeout_event
+ // XXX should this use the GUI event loop (default) or the
+ // MIDI UI event loop ?
+
+ sigc::slot<bool> timeout_slot = sigc::bind
+ (sigc::mem_fun( *this, &MackiePort::handle_control_timeout_event), &control);
+
+ control.in_use_connection = Glib::signal_timeout().connect (timeout_slot , control.in_use_timeout());
+
+ // emit the control event
+ control_event( *this, control, state );
break;
+ }
default:
cerr << "Do not understand control type " << control;
}
}
- catch( MackieControlException & e )
- {
- //cout << bytes << ' ' << e.what() << endl;
+
+ catch( MackieControlException & e ) {
+ MidiByteArray bytes( count, raw_bytes );
+ cout << bytes << ' ' << e.what() << endl;
}
+
+ DEBUG_TRACE (DEBUG::MackieControl, string_compose ("finished MackiePort::handle_midi_any %1\n", bytes));
}