MCP: switch devices on the fly; name MIDI ports appropriately; fix active state; move sysex parsing into Surface
git-svn-id: svn://localhost/ardour2/branches/3.0@11942 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
62620122a9
commit
0c4fe26b41
|
@ -58,45 +58,61 @@ DeviceInfo::set_state (const XMLNode& node, int /* version */)
|
|||
{
|
||||
const XMLProperty* prop;
|
||||
const XMLNode* child;
|
||||
|
||||
|
||||
if (node.name() != "MackieProtocolDevice") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* name is mandatory */
|
||||
|
||||
if ((child = node.child ("Name")) != 0) {
|
||||
if ((prop = child->property ("value")) != 0) {
|
||||
_name = prop->value();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child ("Strips")) != 0) {
|
||||
if ((prop = node.property ("value")) != 0) {
|
||||
if ((prop = child->property ("value")) != 0) {
|
||||
if ((_strip_cnt = atoi (prop->value())) == 0) {
|
||||
_strip_cnt = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child ("Extenders")) != 0) {
|
||||
if ((prop = child->property ("value")) != 0) {
|
||||
if ((_extenders = atoi (prop->value())) == 0) {
|
||||
_extenders = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child ("TwoCharacterDisplay")) != 0) {
|
||||
if ((prop = node.property ("value")) != 0) {
|
||||
if ((prop = child->property ("value")) != 0) {
|
||||
_has_two_character_display = string_is_affirmative (prop->value());
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child ("MasterFader")) != 0) {
|
||||
if ((prop = node.property ("value")) != 0) {
|
||||
if ((prop = child->property ("value")) != 0) {
|
||||
_has_master_fader = string_is_affirmative (prop->value());
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child ("DisplaySegments")) != 0) {
|
||||
if ((prop = node.property ("value")) != 0) {
|
||||
if ((prop = child->property ("value")) != 0) {
|
||||
_has_segmented_display = string_is_affirmative (prop->value());
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child ("TimecodeDisplay")) != 0) {
|
||||
if ((prop = node.property ("value")) != 0) {
|
||||
if ((prop = child->property ("value")) != 0) {
|
||||
_has_timecode_display = string_is_affirmative (prop->value());
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child ("Name")) != 0) {
|
||||
if ((prop = node.property ("value")) != 0) {
|
||||
_name = prop->value();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -112,6 +128,12 @@ DeviceInfo::strip_cnt() const
|
|||
return _strip_cnt;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
DeviceInfo::extenders() const
|
||||
{
|
||||
return _extenders;
|
||||
}
|
||||
|
||||
bool
|
||||
DeviceInfo::has_master_fader() const
|
||||
{
|
||||
|
@ -207,6 +229,7 @@ DeviceInfo::reload_device_info ()
|
|||
|
||||
XMLTree tree;
|
||||
|
||||
|
||||
if (!tree.read (fullpath.c_str())) {
|
||||
continue;
|
||||
}
|
||||
|
@ -216,11 +239,20 @@ DeviceInfo::reload_device_info ()
|
|||
continue;
|
||||
}
|
||||
|
||||
di.set_state (*root, 3000); /* version is ignored for now */
|
||||
|
||||
device_info[di.name()] = di;
|
||||
if (di.set_state (*root, 3000) == 0) { /* version is ignored for now */
|
||||
device_info[di.name()] = di;
|
||||
std::cerr << di << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
delete devinfos;
|
||||
}
|
||||
|
||||
std::ostream& operator<< (std::ostream& os, const Mackie::DeviceInfo& di)
|
||||
{
|
||||
os << di.name() << ' '
|
||||
<< di.strip_cnt() << ' '
|
||||
<< di.extenders() << ' '
|
||||
;
|
||||
return os;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#ifndef __ardour_mackie_control_protocol_device_info_h__
|
||||
#define __ardour_mackie_control_protocol_device_info_h__
|
||||
|
||||
#include <iostream>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
@ -37,6 +38,7 @@ class DeviceInfo
|
|||
int set_state (const XMLNode&, int version);
|
||||
|
||||
uint32_t strip_cnt () const;
|
||||
uint32_t extenders() const;
|
||||
bool has_two_character_display() const;
|
||||
bool has_master_fader () const;
|
||||
bool has_segmented_display() const;
|
||||
|
@ -48,6 +50,7 @@ class DeviceInfo
|
|||
|
||||
private:
|
||||
uint32_t _strip_cnt;
|
||||
uint32_t _extenders;
|
||||
bool _has_two_character_display;
|
||||
bool _has_master_fader;
|
||||
bool _has_segmented_display;
|
||||
|
@ -67,4 +70,6 @@ class DeviceProfile
|
|||
|
||||
}
|
||||
|
||||
std::ostream& operator<< (std::ostream& os, const Mackie::DeviceInfo& di);
|
||||
|
||||
#endif /* __ardour_mackie_control_protocol_device_info_h__ */
|
||||
|
|
|
@ -39,11 +39,9 @@ public:
|
|||
private:
|
||||
|
||||
void surface_combo_changed ();
|
||||
void extenders_changed ();
|
||||
|
||||
MackieControlProtocol& _cp;
|
||||
Gtk::ComboBoxText _surface_combo;
|
||||
Gtk::SpinButton _extenders;
|
||||
};
|
||||
|
||||
void*
|
||||
|
@ -78,46 +76,27 @@ MackieControlProtocolGUI::MackieControlProtocolGUI (MackieControlProtocol& p)
|
|||
table->attach (_surface_combo, 1, 2, 0, 1);
|
||||
|
||||
vector<string> surfaces;
|
||||
|
||||
|
||||
for (std::map<std::string,DeviceInfo>::iterator i = DeviceInfo::device_info.begin(); i != DeviceInfo::device_info.end(); ++i) {
|
||||
std::cerr << "Dveice known: " << i->first << endl;
|
||||
surfaces.push_back (i->first);
|
||||
}
|
||||
Gtkmm2ext::set_popdown_strings (_surface_combo, surfaces);
|
||||
_surface_combo.set_active_text (p.device_info().name());
|
||||
|
||||
_extenders.set_range (0, 8);
|
||||
_extenders.set_increments (1, 4);
|
||||
|
||||
Gtk::Label* l = manage (new Gtk::Label (_("Extenders:")));
|
||||
l->set_alignment (0, 0.5);
|
||||
table->attach (*l, 0, 1, 1, 2);
|
||||
table->attach (_extenders, 1, 2, 1, 2);
|
||||
|
||||
pack_start (*table);
|
||||
|
||||
Gtk::Label* cop_out = manage (new Gtk::Label (_("<i>You must restart Ardour for changes\nto these settings to take effect.</i>")));
|
||||
cop_out->set_use_markup (true);
|
||||
pack_start (*cop_out);
|
||||
|
||||
set_spacing (4);
|
||||
set_border_width (12);
|
||||
show_all ();
|
||||
|
||||
_surface_combo.signal_changed().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::surface_combo_changed));
|
||||
_extenders.signal_changed().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::extenders_changed));
|
||||
}
|
||||
|
||||
void
|
||||
MackieControlProtocolGUI::surface_combo_changed ()
|
||||
{
|
||||
if (_surface_combo.get_active_text() == _("Mackie Control")) {
|
||||
ARDOUR::Config->set_mackie_emulation (X_("mcu"));
|
||||
} else {
|
||||
ARDOUR::Config->set_mackie_emulation (X_("bcf"));
|
||||
}
|
||||
_cp.set_device (_surface_combo.get_active_text());
|
||||
}
|
||||
|
||||
void
|
||||
MackieControlProtocolGUI::extenders_changed ()
|
||||
{
|
||||
ARDOUR::Config->set_mackie_extenders (_extenders.get_value_as_int ());
|
||||
}
|
||||
|
||||
|
|
|
@ -321,7 +321,6 @@ MackieControlProtocol::switch_banks (uint32_t initial, bool force)
|
|||
|
||||
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()));
|
||||
|
@ -497,21 +496,40 @@ MackieControlProtocol::connect_session_signals()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
MackieControlProtocol::set_device (const string& device_name)
|
||||
{
|
||||
map<string,DeviceInfo>::iterator d = DeviceInfo::device_info.find (device_name);
|
||||
|
||||
if (d == DeviceInfo::device_info.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_device_info = d->second;
|
||||
|
||||
surfaces.clear ();
|
||||
create_surfaces ();
|
||||
switch_banks (0, true);
|
||||
}
|
||||
|
||||
void
|
||||
MackieControlProtocol::create_surfaces ()
|
||||
{
|
||||
string device_name = "mcu";
|
||||
string device_name = _device_info.name();
|
||||
surface_type_t stype = mcu;
|
||||
char buf[128];
|
||||
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Create %1 surfaces\n",
|
||||
1 + ARDOUR::Config->get_mackie_extenders()));
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Create %1 surfaces\n", 1 + _device_info.extenders()));
|
||||
|
||||
for (uint32_t n = 0; n < 1 + ARDOUR::Config->get_mackie_extenders(); ++n) {
|
||||
for (uint32_t n = 0; n < 1 + _device_info.extenders(); ++n) {
|
||||
|
||||
boost::shared_ptr<Surface> surface (new Surface (*this, session->engine().jack(), device_name, n, stype));
|
||||
boost::shared_ptr<Surface> surface (new Surface (*this, device_name, n, stype));
|
||||
surfaces.push_back (surface);
|
||||
|
||||
/* next device will be an extender */
|
||||
|
||||
device_name = "mcu_xt";
|
||||
snprintf (buf, sizeof (buf), "%s XT%d", _device_info.name().c_str(), n+1);
|
||||
device_name = buf;
|
||||
stype = ext;
|
||||
|
||||
_input_bundle->add_channel (
|
||||
|
@ -589,10 +607,6 @@ MackieControlProtocol::set_state (const XMLNode & node, int /*version*/)
|
|||
int retval = 0;
|
||||
const XMLProperty* prop;
|
||||
|
||||
if ((prop = node.property (X_("device"))) != 0) {
|
||||
load_device_info (prop->value());
|
||||
}
|
||||
|
||||
// fetch current bank
|
||||
if ((prop = node.property (X_("bank"))) != 0) {
|
||||
string bank = prop->value();
|
||||
|
@ -620,6 +634,10 @@ MackieControlProtocol::set_state (const XMLNode & node, int /*version*/)
|
|||
_f_actions[n] = action;
|
||||
}
|
||||
|
||||
if ((prop = node.property (X_("device"))) != 0) {
|
||||
set_device (prop->value ());
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@ -1147,19 +1165,6 @@ MackieControlProtocol::force_special_route_to_strip (boost::shared_ptr<Route> r,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
MackieControlProtocol::load_device_info (const string& name)
|
||||
{
|
||||
map<string,DeviceInfo>::iterator i = DeviceInfo::device_info.find (name);
|
||||
|
||||
if (i == DeviceInfo::device_info.end()) {
|
||||
error << string_compose (_("No device info for Mackie Control device \"%1\" - ignored"), name) << endmsg;
|
||||
return;
|
||||
}
|
||||
|
||||
_device_info = i->second;
|
||||
}
|
||||
|
||||
void
|
||||
MackieControlProtocol::gui_track_selection_changed (ARDOUR::RouteNotificationListPtr rl)
|
||||
{
|
||||
|
|
|
@ -118,6 +118,7 @@ class MackieControlProtocol
|
|||
const Mackie::DeviceInfo& device_info() const { return _device_info; }
|
||||
|
||||
int set_active (bool yn);
|
||||
void set_device (const std::string&);
|
||||
|
||||
FlipMode flip_mode () const { return _flip_mode; }
|
||||
ViewMode view_mode () const { return _view_mode; }
|
||||
|
@ -269,7 +270,6 @@ class MackieControlProtocol
|
|||
std::vector<std::string> _f_actions;
|
||||
ButtonMap button_map;
|
||||
|
||||
void load_device_info (const std::string&);
|
||||
void create_surfaces ();
|
||||
void port_connected_or_disconnected (std::string, std::string, bool);
|
||||
bool control_in_use_timeout (Mackie::Surface*, Mackie::Control *, Mackie::Control *);
|
||||
|
|
|
@ -38,11 +38,15 @@ using ARDOUR::Panner;
|
|||
using ARDOUR::Pannable;
|
||||
using ARDOUR::PannerShell;
|
||||
|
||||
// The MCU sysex header
|
||||
static MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10);
|
||||
// The MCU sysex header.4th byte Will be overwritten
|
||||
// when we get an incoming sysex that identifies
|
||||
// the device type
|
||||
static MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x14);
|
||||
|
||||
// The MCU extender sysex header
|
||||
static MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11);
|
||||
// The MCU extender sysex header.4th byte Will be overwritten
|
||||
// when we get an incoming sysex that identifies
|
||||
// the device type
|
||||
static MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x15);
|
||||
|
||||
static MidiByteArray empty_midi_byte_array;
|
||||
|
||||
|
@ -121,33 +125,18 @@ static GlobalControlDefinition mackie_global_controls[] = {
|
|||
{ "", 0, Button::factory, "" }
|
||||
};
|
||||
|
||||
Surface::Surface (MackieControlProtocol& mcp, jack_client_t* jack, const std::string& device_name, uint32_t number, surface_type_t stype)
|
||||
Surface::Surface (MackieControlProtocol& mcp, const std::string& device_name, uint32_t number, surface_type_t stype)
|
||||
: _mcp (mcp)
|
||||
, _stype (stype)
|
||||
, _number (number)
|
||||
, _active (false)
|
||||
, _name (device_name)
|
||||
, _active (true)
|
||||
, _connected (false)
|
||||
, _jog_wheel (0)
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MackieControl, "Surface::init\n");
|
||||
|
||||
MIDI::Manager * mm = MIDI::Manager::instance();
|
||||
|
||||
MIDI::Port * input = new MIDI::Port (string_compose (_("%1 in"), device_name), MIDI::Port::IsInput, jack);
|
||||
MIDI::Port * output =new MIDI::Port (string_compose (_("%1 out"), device_name), MIDI::Port::IsOutput, jack);
|
||||
|
||||
input->set_centrally_parsed (false);
|
||||
output->set_centrally_parsed (false);
|
||||
|
||||
mm->add_port (input);
|
||||
mm->add_port (output);
|
||||
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("surface has ports named %1 and %2\n",
|
||||
input->name(), output->name()));
|
||||
|
||||
_port = new SurfacePort (*this, *input, *output);
|
||||
_port->open();
|
||||
_port->inactive_event.connect_same_thread (*this, boost::bind (&Surface::handle_port_inactive, this, _port));
|
||||
_port = new SurfacePort (*this);
|
||||
|
||||
switch (stype) {
|
||||
case mcu:
|
||||
|
@ -177,7 +166,12 @@ Surface::~Surface ()
|
|||
{
|
||||
DEBUG_TRACE (DEBUG::MackieControl, "Surface: destructor\n");
|
||||
|
||||
zero_all ();
|
||||
// faders to minimum
|
||||
write_sysex (0x61);
|
||||
// All LEDs off
|
||||
write_sysex (0x62);
|
||||
// Reset (reboot into offline mode)
|
||||
// _write_sysex (0x63);
|
||||
|
||||
// delete groups
|
||||
for (Groups::iterator it = groups.begin(); it != groups.end(); ++it) {
|
||||
|
@ -307,11 +301,14 @@ Surface::connect_to_signals ()
|
|||
{
|
||||
if (!_connected) {
|
||||
|
||||
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Surface %1 connecting to signals on port %2\n",
|
||||
number(), _port->input_port().name()));
|
||||
|
||||
MIDI::Parser* p = _port->input_port().parser();
|
||||
|
||||
/* Incoming sysex */
|
||||
p->sysex.connect_same_thread (*this, boost::bind (&Surface::handle_midi_sysex, this, _1, _2, _3));
|
||||
/* V-Pot messages are Controller */
|
||||
p->controller.connect_same_thread (*this, boost::bind (&Surface::handle_midi_controller_message, this, _1, _2));
|
||||
/* Button messages are NoteOn */
|
||||
|
@ -435,6 +432,113 @@ Surface::handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes* ev
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
Surface::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count)
|
||||
{
|
||||
MidiByteArray bytes (count, raw_bytes);
|
||||
|
||||
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi_sysex: %1\n", bytes));
|
||||
|
||||
/* always save the device type ID so that our outgoing sysex messages
|
||||
* are correct
|
||||
*/
|
||||
|
||||
if (_stype == mcu) {
|
||||
mackie_sysex_hdr[3] = bytes[4];
|
||||
} else {
|
||||
mackie_sysex_hdr_xt[3] = bytes[4];
|
||||
}
|
||||
|
||||
switch (bytes[5]) {
|
||||
case 0x01:
|
||||
/* MCP: Device Ready
|
||||
LCP: Connection Challenge
|
||||
*/
|
||||
if (bytes[4] == 0x10 || bytes[4] == 0x11) {
|
||||
write_sysex (host_connection_query (bytes));
|
||||
} else {
|
||||
_active = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x03: /* LCP Connection Confirmation */
|
||||
if (bytes[4] == 0x10 || bytes[4] == 0x11) {
|
||||
write_sysex (host_connection_confirmation (bytes));
|
||||
_active = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04: /* LCP: Confirmation Denied */
|
||||
_active = false;
|
||||
break;
|
||||
default:
|
||||
error << "MCP: unknown sysex: " << bytes << endmsg;
|
||||
}
|
||||
}
|
||||
|
||||
static MidiByteArray
|
||||
calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end)
|
||||
{
|
||||
MidiByteArray l;
|
||||
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))));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
// not used right now
|
||||
MidiByteArray
|
||||
Surface::host_connection_query (MidiByteArray & bytes)
|
||||
{
|
||||
MidiByteArray response;
|
||||
|
||||
if (bytes[4] != 0x10 && bytes[4] != 0x11) {
|
||||
/* not a Logic Control device - no response required */
|
||||
return response;
|
||||
}
|
||||
|
||||
// handle host connection query
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host connection query: %1\n", bytes));
|
||||
|
||||
if (bytes.size() != 18) {
|
||||
cerr << "expecting 18 bytes, read " << bytes << " from " << _port->input_port().name() << endl;
|
||||
return response;
|
||||
}
|
||||
|
||||
// build and send host connection reply
|
||||
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);
|
||||
return response;
|
||||
}
|
||||
|
||||
// not used right now
|
||||
MidiByteArray
|
||||
Surface::host_connection_confirmation (const MidiByteArray & bytes)
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host_connection_confirmation: %1\n", bytes));
|
||||
|
||||
// decode host connection confirmation
|
||||
if (bytes.size() != 14) {
|
||||
ostringstream os;
|
||||
os << "expecting 14 bytes, read " << bytes << " from " << _port->input_port().name();
|
||||
throw MackieControlException (os.str());
|
||||
}
|
||||
|
||||
// send version request
|
||||
return MidiByteArray (2, 0x13, 0x00);
|
||||
}
|
||||
|
||||
void
|
||||
Surface::handle_port_inactive (SurfacePort * port)
|
||||
{
|
||||
|
@ -737,3 +841,4 @@ Surface::gui_selection_changed (ARDOUR::RouteNotificationListPtr routes)
|
|||
|
||||
_port->write (msg);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,13 +38,12 @@ class Led;
|
|||
class Surface : public PBD::ScopedConnectionList
|
||||
{
|
||||
public:
|
||||
Surface (MackieControlProtocol&, jack_client_t* jack, const std::string& device_name, uint32_t number, surface_type_t stype);
|
||||
Surface (MackieControlProtocol&, const std::string& name, uint32_t number, surface_type_t stype);
|
||||
virtual ~Surface();
|
||||
|
||||
surface_type_t type() const { return _stype; }
|
||||
uint32_t number() const { return _number; }
|
||||
|
||||
MackieControlProtocol& mcp() const { return _mcp; }
|
||||
const std::string& name() { return _name; }
|
||||
|
||||
bool active() const { return _active; }
|
||||
void drop_routes ();
|
||||
|
@ -148,20 +147,26 @@ public:
|
|||
|
||||
void gui_selection_changed (ARDOUR::RouteNotificationListPtr);
|
||||
|
||||
MackieControlProtocol& mcp() const { return _mcp; }
|
||||
|
||||
protected:
|
||||
void init_controls();
|
||||
void init_strips ();
|
||||
|
||||
private:
|
||||
MackieControlProtocol& _mcp;
|
||||
SurfacePort* _port;
|
||||
surface_type_t _stype;
|
||||
uint32_t _number;
|
||||
bool _active;
|
||||
bool _connected;
|
||||
Mackie::JogWheel* _jog_wheel;
|
||||
SurfacePort* _port;
|
||||
surface_type_t _stype;
|
||||
uint32_t _number;
|
||||
std::string _name;
|
||||
bool _active;
|
||||
bool _connected;
|
||||
Mackie::JogWheel* _jog_wheel;
|
||||
|
||||
void jog_wheel_state_display (Mackie::JogWheel::State state);
|
||||
void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count);
|
||||
MidiByteArray host_connection_query (MidiByteArray& bytes);
|
||||
MidiByteArray host_connection_confirmation (const MidiByteArray& bytes);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -29,9 +29,11 @@
|
|||
|
||||
#include "ardour/debug.h"
|
||||
#include "ardour/rc_configuration.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/audioengine.h"
|
||||
|
||||
#include "controls.h"
|
||||
#include "mackie_control_exception.h"
|
||||
#include "mackie_control_protocol.h"
|
||||
#include "surface.h"
|
||||
#include "surface_port.h"
|
||||
|
||||
|
@ -42,22 +44,35 @@ using namespace std;
|
|||
using namespace Mackie;
|
||||
using namespace PBD;
|
||||
|
||||
/** @param input_port Input MIDI::Port; this object takes responsibility for removing it from
|
||||
* the MIDI::Manager and destroying it.
|
||||
* @param output_port Output MIDI::Port; responsibility similarly taken.
|
||||
/** @param input_port Input MIDI::Port; this object takes responsibility for
|
||||
* adding & removing it from the MIDI::Manager and destroying it. @param
|
||||
* output_port Output MIDI::Port; responsibility similarly taken.
|
||||
*/
|
||||
SurfacePort::SurfacePort (Surface& s, MIDI::Port & input_port, MIDI::Port & output_port)
|
||||
SurfacePort::SurfacePort (Surface& s)
|
||||
: _surface (&s)
|
||||
, _input_port (&input_port)
|
||||
, _output_port (&output_port)
|
||||
, _active (false)
|
||||
{
|
||||
jack_client_t* jack = MackieControlProtocol::instance()->get_session().engine().jack();
|
||||
|
||||
_input_port = new MIDI::Port (string_compose (_("%1 in"), _surface->name()), MIDI::Port::IsInput, jack);
|
||||
_output_port =new MIDI::Port (string_compose (_("%1 out"), _surface->name()), MIDI::Port::IsOutput, jack);
|
||||
|
||||
/* MackieControl has its own thread for handling input from the input
|
||||
* port, and we don't want anything handling output from the output
|
||||
* port. This stops the Generic MIDI UI event loop in ardour from
|
||||
* attempting to handle these ports.
|
||||
*/
|
||||
|
||||
_input_port->set_centrally_parsed (false);
|
||||
_output_port->set_centrally_parsed (false);
|
||||
|
||||
MIDI::Manager * mm = MIDI::Manager::instance();
|
||||
|
||||
mm->add_port (_input_port);
|
||||
mm->add_port (_output_port);
|
||||
}
|
||||
|
||||
SurfacePort::~SurfacePort()
|
||||
{
|
||||
close ();
|
||||
|
||||
MIDI::Manager* mm = MIDI::Manager::instance ();
|
||||
|
||||
if (_input_port) {
|
||||
|
@ -78,183 +93,38 @@ string fetch_errmsg (int error_number)
|
|||
return msg;
|
||||
}
|
||||
|
||||
MidiByteArray SurfacePort::read()
|
||||
{
|
||||
const int max_buf_size = 512;
|
||||
MIDI::byte buf[max_buf_size];
|
||||
MidiByteArray retval;
|
||||
|
||||
// check active. Mainly so that the destructor
|
||||
// doesn't destroy the mutex while it's still locked
|
||||
if (!active()) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
// return nothing read if the lock isn't acquired
|
||||
|
||||
// read port and copy to return value
|
||||
int nread = input_port().read (buf, sizeof (buf));
|
||||
|
||||
if (nread >= 0) {
|
||||
retval.copy (nread, buf);
|
||||
if ((size_t) nread == sizeof (buf)) {
|
||||
#ifdef PORT_DEBUG
|
||||
cout << "SurfacePort::read recursive" << endl;
|
||||
#endif
|
||||
retval << read();
|
||||
}
|
||||
} else {
|
||||
if (errno != EAGAIN) {
|
||||
ostringstream os;
|
||||
os << "Surface: error reading from port: " << input_port().name();
|
||||
os << ": " << errno << fetch_errmsg (errno);
|
||||
|
||||
cout << os.str() << endl;
|
||||
inactive_event();
|
||||
throw MackieControlException (os.str());
|
||||
}
|
||||
}
|
||||
#ifdef PORT_DEBUG
|
||||
cout << "SurfacePort::read: " << retval << endl;
|
||||
#endif
|
||||
return retval;
|
||||
}
|
||||
|
||||
void SurfacePort::write (const MidiByteArray & mba)
|
||||
int
|
||||
SurfacePort::write (const MidiByteArray & mba)
|
||||
{
|
||||
if (mba.empty()) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!active()) return;
|
||||
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("port %1 write %2\n", output_port().name(), mba));
|
||||
|
||||
int count = output_port().write (mba.bytes().get(), mba.size(), 0);
|
||||
|
||||
if (count != (int)mba.size()) {
|
||||
|
||||
if (errno == 0) {
|
||||
|
||||
cout << "port overflow on " << output_port().name() << ". Did not write all of " << mba << endl;
|
||||
|
||||
} else if (errno != EAGAIN) {
|
||||
ostringstream os;
|
||||
os << "Surface: couldn't write to port " << output_port().name();
|
||||
os << ", error: " << fetch_errmsg (errno) << "(" << errno << ")";
|
||||
|
||||
cout << os.str() << endl;
|
||||
inactive_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SurfacePort::open()
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::open %1\n", *this));
|
||||
input_port().parser()->sysex.connect_same_thread (sysex_connection, boost::bind (&SurfacePort::handle_midi_sysex, this, _1, _2, _3));
|
||||
_active = true;
|
||||
}
|
||||
|
||||
void SurfacePort::close()
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MackieControl, "SurfacePort::close\n");
|
||||
sysex_connection.disconnect();
|
||||
|
||||
if (_surface) {
|
||||
// faders to minimum
|
||||
_surface->write_sysex (0x61);
|
||||
// All LEDs off
|
||||
_surface->write_sysex (0x62);
|
||||
// Reset (reboot into offline mode)
|
||||
_surface->write_sysex (0x63);
|
||||
return -1;
|
||||
}
|
||||
|
||||
_active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
SurfacePort::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count)
|
||||
{
|
||||
MidiByteArray bytes (count, raw_bytes);
|
||||
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi_sysex: %1\n", bytes));
|
||||
|
||||
switch (bytes[5])
|
||||
{
|
||||
case 0x01:
|
||||
_surface->write_sysex (host_connection_query (bytes));
|
||||
break;
|
||||
case 0x03:
|
||||
// not used right now
|
||||
_surface->write_sysex (host_connection_confirmation (bytes));
|
||||
break;
|
||||
case 0x04:
|
||||
inactive_event ();
|
||||
cout << "host connection error" << bytes << endl;
|
||||
break;
|
||||
case 0x14:
|
||||
// probe_emulation (bytes);
|
||||
break;
|
||||
default:
|
||||
cout << "unknown sysex: " << bytes << endl;
|
||||
}
|
||||
}
|
||||
|
||||
MidiByteArray calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end)
|
||||
{
|
||||
MidiByteArray l;
|
||||
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))));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
// not used right now
|
||||
MidiByteArray SurfacePort::host_connection_query (MidiByteArray & bytes)
|
||||
{
|
||||
MidiByteArray response;
|
||||
|
||||
// handle host connection query
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host connection query: %1\n", bytes));
|
||||
|
||||
if (bytes.size() != 18) {
|
||||
cerr << "expecting 18 bytes, read " << bytes << " from " << input_port().name() << endl;
|
||||
return response;
|
||||
}
|
||||
|
||||
// build and send host connection reply
|
||||
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);
|
||||
return response;
|
||||
}
|
||||
|
||||
// not used right now
|
||||
MidiByteArray SurfacePort::host_connection_confirmation (const MidiByteArray & bytes)
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host_connection_confirmation: %1\n", bytes));
|
||||
|
||||
// decode host connection confirmation
|
||||
if (bytes.size() != 14) {
|
||||
ostringstream os;
|
||||
os << "expecting 14 bytes, read " << bytes << " from " << input_port().name();
|
||||
throw MackieControlException (os.str());
|
||||
}
|
||||
|
||||
// send version request
|
||||
return MidiByteArray (2, 0x13, 0x00);
|
||||
}
|
||||
|
||||
|
||||
ostream & Mackie::operator << (ostream & os, const SurfacePort & port)
|
||||
ostream &
|
||||
Mackie::operator << (ostream & os, const SurfacePort & port)
|
||||
{
|
||||
os << "{ ";
|
||||
os << "name: " << port.input_port().name() << " " << port.output_port().name();
|
||||
|
|
|
@ -44,42 +44,23 @@ class Surface;
|
|||
class SurfacePort
|
||||
{
|
||||
public:
|
||||
SurfacePort (Mackie::Surface&, MIDI::Port& input_port, MIDI::Port& output_port);
|
||||
SurfacePort (Mackie::Surface&);
|
||||
virtual ~SurfacePort();
|
||||
|
||||
void open();
|
||||
void close();
|
||||
|
||||
/// read bytes from the port. They'll either end up in the
|
||||
/// parser, or if that's not active they'll be returned
|
||||
MidiByteArray read();
|
||||
|
||||
/// an easier way to output bytes via midi
|
||||
void write (const MidiByteArray&);
|
||||
int write (const MidiByteArray&);
|
||||
|
||||
MIDI::Port& input_port() { return *_input_port; }
|
||||
const MIDI::Port& input_port() const { return *_input_port; }
|
||||
MIDI::Port& output_port() { return *_output_port; }
|
||||
const MIDI::Port& output_port() const { return *_output_port; }
|
||||
|
||||
// emitted when the port goes inactive (ie a read or write failed)
|
||||
PBD::Signal0<void> inactive_event;
|
||||
|
||||
void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count);
|
||||
|
||||
bool active() const { return _active; }
|
||||
|
||||
protected:
|
||||
MidiByteArray host_connection_query (MidiByteArray& bytes);
|
||||
MidiByteArray host_connection_confirmation (const MidiByteArray& bytes);
|
||||
|
||||
private:
|
||||
Mackie::Surface* _surface;
|
||||
MIDI::Port* _input_port;
|
||||
MIDI::Port* _output_port;
|
||||
bool _active;
|
||||
|
||||
PBD::ScopedConnection sysex_connection;
|
||||
};
|
||||
|
||||
std::ostream& operator << (std::ostream& , const SurfacePort& port);
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
<SegmentedDisplay value="no"/>
|
||||
<TimecodeDisplay value="yes"/>
|
||||
<TwoCharacterDisplay value="yes"/>
|
||||
<Extenders value="0"/>
|
||||
</MackieProtocolDevice>
|
||||
|
|
Loading…
Reference in New Issue
Block a user