13
0

Mackie Control Surface: Improved support for iCON QCon Pro X

Support the second LCD on the Pro X and use it for channel strip labels.

Support the stereo master meters on the Pro X
This commit is contained in:
Todd Naugle 2021-07-02 16:00:51 -05:00
parent 7a5ccdd3c3
commit 45f9b45bff
12 changed files with 395 additions and 34 deletions

View File

@ -60,6 +60,8 @@ DeviceInfo::DeviceInfo()
, _uses_ipmidi (false)
, _no_handshake (false)
, _is_qcon(false)
, _has_qcon_second_lcd(false)
, _has_qcon_master_meters(false)
, _has_meters (true)
, _has_separate_meters (false)
, _single_fader_follows_selection (false)
@ -337,6 +339,18 @@ DeviceInfo::set_state (const XMLNode& node, int /* version */)
_is_qcon = false;
}
if ((child = node.child ("HasQConSecondLCD")) != 0) {
child->get_property ("value", _has_qcon_second_lcd);
} else {
_has_qcon_second_lcd = false;
}
if ((child = node.child ("HasQConMasterMeters")) != 0) {
child->get_property ("value", _has_qcon_master_meters);
} else {
_has_qcon_master_meters = false;
}
if ((child = node.child ("HasSeparateMeters")) != 0) {
child->get_property ("value", _has_separate_meters);
} else {
@ -485,6 +499,18 @@ DeviceInfo::is_qcon () const
return _is_qcon;
}
bool
DeviceInfo::has_qcon_second_lcd () const
{
return _has_qcon_second_lcd;
}
bool
DeviceInfo::has_qcon_master_meters () const
{
return _has_qcon_master_meters;
}
bool
DeviceInfo::has_touch_sense_faders () const
{

View File

@ -81,6 +81,8 @@ class DeviceInfo
bool uses_ipmidi() const;
bool no_handshake() const;
bool is_qcon() const;
bool has_qcon_second_lcd() const;
bool has_qcon_master_meters() const;
bool has_meters() const;
bool has_separate_meters() const;
bool single_fader_follows_selection() const;
@ -112,6 +114,8 @@ class DeviceInfo
bool _uses_ipmidi;
bool _no_handshake;
bool _is_qcon;
bool _has_qcon_second_lcd;
bool _has_qcon_master_meters;
bool _has_meters;
bool _has_separate_meters;
bool _single_fader_follows_selection;

View File

@ -107,6 +107,8 @@ Strip::Strip (Surface& s, const std::string& name, int index, const map<Button::
, _controls_locked (false)
, _transport_is_rolling (false)
, _metering_active (true)
, _lcd2_available (true)
, _lcd2_label_pitch (7)
, _block_screen_redisplay_until (0)
, return_to_vpot_mode_display_at (UINT64_MAX)
, _pan_mode (PanAzimuthAutomation)
@ -122,6 +124,16 @@ Strip::Strip (Surface& s, const std::string& name, int index, const map<Button::
_meter = dynamic_cast<Meter*> (Meter::factory (*_surface, index, "meter", *this));
}
if (s.mcp().device_info().has_qcon_second_lcd()) {
_lcd2_available = true;
// The main unit has 9 faders under the second display.
// Extenders have 8 faders.
if (s.mcp().device_info().has_master_fader()) {
_lcd2_label_pitch = 6;
}
}
for (map<Button::ID,StripButtonInfo>::const_iterator b = strip_buttons.begin(); b != strip_buttons.end(); ++b) {
Button* bb = dynamic_cast<Button*> (Button::factory (*_surface, b->first, b->second.base_id + index, b->second.name, *this));
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("surface %1 strip %2 new button BID %3 id %4 from base %5\n",
@ -416,6 +428,14 @@ Strip::show_stripable_name ()
} else {
pending_display[0] = PBD::short_version (fullname, 6);
}
if (_lcd2_available) {
if (fullname.length() <= (_lcd2_label_pitch - 1)) {
lcd2_pending_display[0] = fullname;
} else {
lcd2_pending_display[0] = PBD::short_version (fullname, (_lcd2_label_pitch - 1));
}
}
}
void
@ -497,7 +517,7 @@ Strip::select_event (Button&, ButtonState bs)
if (ms & MackieControlProtocol::MODIFIER_CMDALT) {
_controls_locked = !_controls_locked;
_surface->write (display (1,_controls_locked ? "Locked" : "Unlock"));
_surface->write (display (0, 1,_controls_locked ? "Locked" : "Unlock"));
block_vpot_mode_display_for (1000);
return;
}
@ -850,7 +870,7 @@ Strip::redisplay (PBD::microseconds_t now, bool force)
}
if (force || (current_display[0] != pending_display[0])) {
_surface->write (display (0, pending_display[0]));
_surface->write (display (0, 0, pending_display[0]));
current_display[0] = pending_display[0];
}
@ -860,9 +880,20 @@ Strip::redisplay (PBD::microseconds_t now, bool force)
}
if (force || (current_display[1] != pending_display[1])) {
_surface->write (display (1, pending_display[1]));
_surface->write (display (0, 1, pending_display[1]));
current_display[1] = pending_display[1];
}
if (_lcd2_available) {
if (force || (lcd2_current_display[0] != lcd2_pending_display[0])) {
_surface->write (display (1, 0, lcd2_pending_display[0]));
lcd2_current_display[0] = lcd2_pending_display[0];
}
if (force || (lcd2_current_display[1] != lcd2_pending_display[1])) {
_surface->write (display (1, 1, lcd2_pending_display[1]));
lcd2_current_display[1] = lcd2_pending_display[1];
}
}
}
void
@ -920,52 +951,102 @@ Strip::zero ()
_surface->write ((*it)->zero ());
}
_surface->write (blank_display (0));
_surface->write (blank_display (1));
_surface->write (blank_display (0, 0));
_surface->write (blank_display (0, 1));
pending_display[0] = string();
pending_display[1] = string();
current_display[0] = string();
current_display[1] = string();
if (_lcd2_available) {
_surface->write (blank_display (1, 0));
_surface->write (blank_display (1, 1));
lcd2_pending_display[0] = string();
lcd2_pending_display[1] = string();
lcd2_current_display[0] = string();
lcd2_current_display[1] = string();
}
}
MidiByteArray
Strip::blank_display (uint32_t line_number)
Strip::blank_display (uint32_t lcd_number, uint32_t line_number)
{
return display (line_number, string());
return display (lcd_number, line_number, string());
}
MidiByteArray
Strip::display (uint32_t line_number, const std::string& line)
Strip::display (uint32_t lcd_number, uint32_t line_number, const std::string& line)
{
assert (line_number <= 1);
bool add_left_pad_char = false;
unsigned left_pad_offset = 0;
unsigned lcd_label_pitch = 7;
unsigned max_char_count = lcd_label_pitch - 1;
MidiByteArray retval;
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip_display index: %1, line %2 = %3\n", _index, line_number, line));
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip_display lcd: %1, index: %2, line %3 = %4\n"
, lcd_number, _index, line_number, line));
// sysex header
if (lcd_number == 0) {
// Standard MCP display
retval << _surface->sysex_hdr();
// code for display
retval << 0x12;
}
else {
/* The second lcd on the Qcon Pro X master unit uses a 6 character label instead of 7.
* That allows a 9th label for the master fader.
*
* Format: _6Char#1_6Char#2_6Char#3_6Char#4_6Char#5_6Char#6_6Char#7_6Char#8_6Char#9_
*
* The _ in the format is a space that is inserted as label display seperators
*
* The extender unit has 8 faders and uses the standard MCP pitch.
*
* The second LCD is an extention to the MCP with a different sys ex header.
*/
lcd_label_pitch = _lcd2_label_pitch;
max_char_count = lcd_label_pitch - 1;
retval << MidiByteArray (5, MIDI::sysex, 0x0, 0x0, 0x67, 0x15);
// code for display
retval << 0x13;
if (lcd_label_pitch == 6) {
if (_index == 0) {
add_left_pad_char = true;
}
else {
left_pad_offset = 1;
}
}
}
// offset (0 to 0x37 first line, 0x38 to 0x6f for second line)
retval << (_index * 7 + (line_number * 0x38));
retval << (_index * lcd_label_pitch + (line_number * 0x38) + left_pad_offset);
if (add_left_pad_char) {
retval << ' '; // add the left pad space
}
// ascii data to display. @param line is UTF-8
string ascii = Glib::convert_with_fallback (line, "UTF-8", "ISO-8859-1", "_");
string::size_type len = ascii.length();
if (len > 6) {
ascii = ascii.substr (0, 6);
len = 6;
if (len > max_char_count) {
ascii = ascii.substr (0, max_char_count);
len = max_char_count;
}
retval << ascii;
// pad with " " out to 6 chars
for (int i = len; i < 6; ++i) {
// pad with " " out to N chars
for (unsigned i = len; i < max_char_count; ++i) {
retval << ' ';
}
// column spacer, unless it's the right-hand column
if (_index < 7) {
if (_index < 7 || lcd_number == 1) {
retval << ' ';
}

View File

@ -91,8 +91,8 @@ public:
void periodic (PBD::microseconds_t now_usecs);
void redisplay (PBD::microseconds_t now_usecs, bool force = true);
MidiByteArray display (uint32_t line_number, const std::string&);
MidiByteArray blank_display (uint32_t line_number);
MidiByteArray display (uint32_t lcd_number, uint32_t line_number, const std::string&);
MidiByteArray blank_display (uint32_t lcd_number, uint32_t line_number);
static std::string format_paramater_for_display(
ARDOUR::ParameterDescriptor const& desc,
@ -136,8 +136,12 @@ private:
bool _controls_locked;
bool _transport_is_rolling;
bool _metering_active;
bool _lcd2_available;
uint32_t _lcd2_label_pitch; // number of label characters including the required space between strips
std::string pending_display[2];
std::string current_display[2];
std::string lcd2_pending_display[2];
std::string lcd2_current_display[2];
PBD::microseconds_t _block_screen_redisplay_until;
PBD::microseconds_t return_to_vpot_mode_display_at;
boost::shared_ptr<ARDOUR::Stripable> _stripable;

View File

@ -34,13 +34,16 @@
#include "ardour/audioengine.h"
#include "ardour/automation_control.h"
#include "ardour/chan_count.h"
#include "ardour/debug.h"
#include "ardour/route.h"
#include "ardour/meter.h"
#include "ardour/panner.h"
#include "ardour/panner_shell.h"
#include "ardour/profile.h"
#include "ardour/rc_configuration.h"
#include "ardour/session.h"
#include "ardour/types.h"
#include "ardour/utils.h"
#include <gtkmm2ext/gui_thread.h>
@ -72,6 +75,7 @@ using ARDOUR::Stripable;
using ARDOUR::Panner;
using ARDOUR::Profile;
using ARDOUR::AutomationControl;
using ARDOUR::ChanCount;
using namespace ArdourSurface;
using namespace Mackie;
@ -108,6 +112,8 @@ Surface::Surface (MackieControlProtocol& mcp, const std::string& device_name, ui
, _jog_wheel (0)
, _master_fader (0)
, _last_master_gain_written (-0.0f)
, _has_master_display (false)
, _has_master_meter (false)
, connection_state (0)
, is_qcon (false)
, input_source (0)
@ -123,6 +129,8 @@ Surface::Surface (MackieControlProtocol& mcp, const std::string& device_name, ui
//Store Qcon flag
if( mcp.device_info().is_qcon() ) {
is_qcon = true;
_has_master_display = (mcp.device_info().has_master_fader() && mcp.device_info().has_qcon_second_lcd());
_has_master_meter = mcp.device_info().has_qcon_master_meters();
} else {
is_qcon = false;
}
@ -405,13 +413,11 @@ Surface::master_monitor_may_have_changed ()
void
Surface::setup_master ()
{
boost::shared_ptr<Stripable> m;
if ((m = _mcp.get_session().monitor_out()) == 0) {
m = _mcp.get_session().master_out();
if ((_master_stripable = _mcp.get_session().monitor_out()) == 0) {
_master_stripable = _mcp.get_session().master_out();
}
if (!m) {
if (!_master_stripable) {
if (_master_fader) {
_master_fader->set_control (boost::shared_ptr<AutomationControl>());
}
@ -423,6 +429,7 @@ Surface::setup_master ()
Groups::iterator group_it;
Group* master_group;
group_it = groups.find("master");
DeviceInfo device_info = _mcp.device_info();
if (group_it == groups.end()) {
groups["master"] = master_group = new Group ("master");
@ -430,9 +437,8 @@ Surface::setup_master ()
master_group = group_it->second;
}
_master_fader = dynamic_cast<Fader*> (Fader::factory (*this, _mcp.device_info().strip_cnt(), "master", *master_group));
_master_fader = dynamic_cast<Fader*> (Fader::factory (*this, device_info.strip_cnt(), "master", *master_group));
DeviceInfo device_info = _mcp.device_info();
GlobalButtonInfo master_button = device_info.get_global_button(Button::MasterFaderTouch);
Button* bb = dynamic_cast<Button*> (Button::factory (
*this,
@ -448,10 +454,15 @@ Surface::setup_master ()
master_connection.disconnect ();
}
_master_fader->set_control (m->gain_control());
m->gain_control()->Changed.connect (master_connection, MISSING_INVALIDATOR, boost::bind (&Surface::master_gain_changed, this), ui_context());
_master_fader->set_control (_master_stripable->gain_control());
_master_stripable->gain_control()->Changed.connect (master_connection, MISSING_INVALIDATOR, boost::bind (&Surface::master_gain_changed, this), ui_context());
_last_master_gain_written = FLT_MAX; /* some essentially impossible value */
master_gain_changed ();
if (_has_master_display) {
_master_stripable->PropertyChanged.connect (master_connection, MISSING_INVALIDATOR, boost::bind (&Surface::master_property_changed, this, _1), ui_context());
show_master_name();
}
}
void
@ -477,6 +488,133 @@ Surface::master_gain_changed ()
_last_master_gain_written = normalized_position;
}
void
Surface::master_property_changed (const PropertyChange& what_changed)
{
if (what_changed.contains (ARDOUR::Properties::name)) {
DEBUG_TRACE (DEBUG::MackieControl, "master_property_changed\n");
string fullname = string();
if (!_master_stripable) {
fullname = string();
} else {
fullname = _master_stripable->name();
}
if (fullname.length() <= 6) {
pending_display[0] = fullname;
} else {
pending_display[0] = PBD::short_version (fullname, 6);
}
}
}
void
Surface::master_meter_changed ()
{
if (!_has_master_meter) {
return;
}
if (!_master_stripable) {
return;
}
ChanCount count = _master_stripable->peak_meter()->output_streams();
for (unsigned i = 0; i < 2 && i < count.n_audio(); ++i) {
int segment;
float dB = _master_stripable->peak_meter()->meter_level (i, ARDOUR::MeterPeak);
std::pair<bool,float> result = Meter::calculate_meter_over_and_deflection(dB);
MidiByteArray msg;
/* we can use up to 13 segments */
segment = lrintf ((result.second/115.0) * 13.0);
_port->write (MidiByteArray (2, 0xd1, (i<<4) | segment));
}
}
void
Surface::show_master_name ()
{
string fullname = string();
if (!_master_stripable) {
fullname = string();
} else {
fullname = _master_stripable->name();
}
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("show_master_name: name %1\n", fullname));
if (fullname.length() <= 6) {
pending_display[0] = fullname;
} else {
pending_display[0] = PBD::short_version (fullname, 6);
}
}
MidiByteArray
Surface::master_display (uint32_t line_number, const std::string& line)
{
/* The second lcd on the Qcon Pro X master unit uses a 6 character label instead of 7.
* That allows a 9th label for the master fader and since there is a space at the end
* use all 6 characters for text.
*
* Format: _6Char#1_6Char#2_6Char#3_6Char#4_6Char#5_6Char#6_6Char#7_6Char#8_6Char#9_
*
* The _ in the format is a space that is inserted as label display seperators
*
* The second LCD is an extention to the MCP with a different sys ex header.
*/
MidiByteArray retval;
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("master display: line %1 = %2\n", line_number, line));
retval << MidiByteArray (5, MIDI::sysex, 0x0, 0x0, 0x67, 0x15);
// code for display
retval << 0x13;
// offset (0 to 0x37 first line, 0x38 to 0x6f for second line)
retval << (49 + (line_number * 0x38)); // 9th position
// ascii data to display. @param line is UTF-8
string ascii = Glib::convert_with_fallback (line, "UTF-8", "ISO-8859-1", "_");
string::size_type len = ascii.length();
if (len > 6) {
ascii = ascii.substr (0, 6);
len = 5;
}
retval << ascii;
// pad with " " out to N chars
for (unsigned i = len; i < 6; ++i) {
retval << ' ';
}
// Space as the last character
retval << ' ';
// sysex trailer
retval << MIDI::eox;
return retval;
}
MidiByteArray
Surface::blank_master_display (uint32_t line_number)
{
if (line_number == 0) {
return MidiByteArray (15, MIDI::sysex, 0x0, 0x0, 0x67, 0x15, 0x13, 0x31
, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, MIDI::eox);
}
else {
return MidiByteArray (15, MIDI::sysex, 0x0, 0x0, 0x67, 0x15, 0x13, 0x69
, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, MIDI::eox);
}
}
float
Surface::scaled_delta (float delta, float current_speed)
{
@ -918,6 +1056,21 @@ Surface::zero_all ()
if (_mcp.device_info().has_master_fader () && _master_fader) {
_port->write (_master_fader->zero ());
if (_has_master_display) {
DEBUG_TRACE (DEBUG::MackieControl, "Surface::zero_all: Clearing Master display\n");
_port->write (blank_master_display(0));
_port->write (blank_master_display(1));
pending_display[0] = string();
pending_display[1] = string();
current_display[0] = string();
current_display[1] = string();
}
if (_has_master_meter) {
_port->write (MidiByteArray (2, 0xd1, 0x00));
_port->write (MidiByteArray (2, 0xd1, 0x10));
}
}
// zero all strips
@ -954,6 +1107,7 @@ void
Surface::periodic (PBD::microseconds_t now_usecs)
{
master_gain_changed();
master_meter_changed();
for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
(*s)->periodic (now_usecs);
}
@ -962,6 +1116,20 @@ Surface::periodic (PBD::microseconds_t now_usecs)
void
Surface::redisplay (PBD::microseconds_t now, bool force)
{
if (_has_master_display) {
if (force || (current_display[0] != pending_display[0])) {
DEBUG_TRACE (DEBUG::MackieControl, "Surface::redisplay: Updating master display line 0\n");
write (master_display (0, pending_display[0]));
current_display[0] = pending_display[0];
}
if (force || (current_display[1] != pending_display[1])) {
DEBUG_TRACE (DEBUG::MackieControl, "Surface::redisplay: Updating master display line 1\n");
write (master_display (1, pending_display[1]));
current_display[1] = pending_display[1];
}
}
for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
(*s)->redisplay (now, force);
}
@ -1102,6 +1270,7 @@ Surface::update_flip_mode_display ()
void
Surface::subview_mode_changed ()
{
show_master_name();
for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
(*s)->subview_mode_changed ();
}

View File

@ -24,6 +24,7 @@
#include <sigc++/trackable.h>
#include "pbd/property_basics.h"
#include "pbd/signals.h"
#include "pbd/xml++.h"
#include "midi++/types.h"
@ -209,6 +210,11 @@ public:
Fader* _master_fader;
float _last_master_gain_written;
PBD::ScopedConnection master_connection;
bool _has_master_display;
bool _has_master_meter;
boost::shared_ptr<ARDOUR::Stripable> _master_stripable;
std::string pending_display[2];
std::string current_display[2];
void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count);
MidiByteArray host_connection_query (MidiByteArray& bytes);
@ -219,6 +225,11 @@ public:
void init_strips (uint32_t n);
void setup_master ();
void master_gain_changed ();
void master_property_changed (const PBD::PropertyChange&);
void master_meter_changed ();
void show_master_name();
MidiByteArray master_display (uint32_t line_number, const std::string&); // QCon ProX 2nd LCD master label
MidiByteArray blank_master_display (uint32_t line_number);
enum ConnectionState {
InputConnected = 0x1,

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<MackieProtocolDevice>
<Name value="iCON QCon Pro and QCon Expander on Right"/>
<Name value="iCON QCon Pro X + Pro XS on right"/>
<Strips value="8"/>
<Extenders value="1"/>
<MasterPosition value="1"/>
<MasterFader value="yes"/>
<TimecodeDisplay value="yes"/>
<TwoCharacterDisplay value="no"/>
<HasSeparateMeters value="yes"/>
<GlobalControls value="yes"/>
<JogWheel value="yes"/>
<TouchSenseFaders value="yes"/>
@ -13,4 +15,6 @@
<usesIPMIDI value="no"/>
<NoHandShake value="yes"/>
<IsQCon value="yes"/>
<HasQConSecondLCD value="yes"/>
<HasQConMasterMeters value="yes"/>
</MackieProtocolDevice>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<MackieProtocolDevice>
<Name value="iCON QCon Pro"/>
<Name value="iCON QCon Pro X"/>
<Strips value="8"/>
<Extenders value="0"/>
<MasterPosition value="0"/>
<MasterFader value="yes"/>
<TimecodeDisplay value="yes"/>
<TwoCharacterDisplay value="no"/>
<HasSeparateMeters value="yes"/>
<GlobalControls value="yes"/>
<JogWheel value="yes"/>
<TouchSenseFaders value="yes"/>
@ -13,4 +15,6 @@
<usesIPMIDI value="no"/>
<NoHandShake value="yes"/>
<IsQCon value="yes"/>
<HasQConSecondLCD value="yes"/>
<HasQConMasterMeters value="yes"/>
</MackieProtocolDevice>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MackieProtocolDevice>
<Name value="iCON QCon Pro G2 + Pro G2 XS on right"/>
<Strips value="8"/>
<Extenders value="1"/>
<MasterPosition value="1"/>
<MasterFader value="yes"/>
<TimecodeDisplay value="yes"/>
<TwoCharacterDisplay value="no"/>
<HasSeparateMeters value="yes"/>
<GlobalControls value="yes"/>
<JogWheel value="yes"/>
<TouchSenseFaders value="yes"/>
<LogicControlButtons value="yes"/>
<usesIPMIDI value="no"/>
<NoHandShake value="yes"/>
<IsQCon value="yes"/>
</MackieProtocolDevice>

18
share/mcp/qcon_g2.device Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MackieProtocolDevice>
<Name value="iCON QCon Pro G2"/>
<Strips value="8"/>
<Extenders value="0"/>
<MasterPosition value="0"/>
<MasterFader value="yes"/>
<TimecodeDisplay value="yes"/>
<TwoCharacterDisplay value="no"/>
<HasSeparateMeters value="yes"/>
<GlobalControls value="yes"/>
<JogWheel value="yes"/>
<TouchSenseFaders value="yes"/>
<LogicControlButtons value="yes"/>
<usesIPMIDI value="no"/>
<NoHandShake value="yes"/>
<IsQCon value="yes"/>
</MackieProtocolDevice>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MackieProtocolDevice>
<Name value="iCON QCon Pro G2 + Pro G2 XS on left"/>
<Strips value="8"/>
<Extenders value="1"/>
<MasterPosition value="2"/>
<MasterFader value="yes"/>
<TimecodeDisplay value="yes"/>
<TwoCharacterDisplay value="no"/>
<HasSeparateMeters value="yes"/>
<GlobalControls value="yes"/>
<JogWheel value="yes"/>
<TouchSenseFaders value="yes"/>
<LogicControlButtons value="yes"/>
<usesIPMIDI value="no"/>
<NoHandShake value="yes"/>
<IsQCon value="yes"/>
</MackieProtocolDevice>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<MackieProtocolDevice>
<Name value="iCON QCon Pro and QCon Expander on Left"/>
<Name value="iCON QCon Pro X + Pro XS on left"/>
<Strips value="8"/>
<Extenders value="1"/>
<MasterPosition value="2"/>
<MasterFader value="yes"/>
<TimecodeDisplay value="yes"/>
<TwoCharacterDisplay value="no"/>
<HasSeparateMeters value="yes"/>
<GlobalControls value="yes"/>
<JogWheel value="yes"/>
<TouchSenseFaders value="yes"/>
@ -13,4 +15,6 @@
<usesIPMIDI value="no"/>
<NoHandShake value="yes"/>
<IsQCon value="yes"/>
<HasQConSecondLCD value="yes"/>
<HasQConMasterMeters value="yes"/>
</MackieProtocolDevice>