diff --git a/libs/surfaces/mackie/device_info.cc b/libs/surfaces/mackie/device_info.cc index 820b2dc4f7..c5d3c68050 100644 --- a/libs/surfaces/mackie/device_info.cc +++ b/libs/surfaces/mackie/device_info.cc @@ -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 { diff --git a/libs/surfaces/mackie/device_info.h b/libs/surfaces/mackie/device_info.h index db0837f26c..da00bff9a9 100644 --- a/libs/surfaces/mackie/device_info.h +++ b/libs/surfaces/mackie/device_info.h @@ -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; diff --git a/libs/surfaces/mackie/strip.cc b/libs/surfaces/mackie/strip.cc index 78bb59eb87..7f46431cdc 100644 --- a/libs/surfaces/mackie/strip.cc +++ b/libs/surfaces/mackie/strip.cc @@ -107,6 +107,8 @@ Strip::Strip (Surface& s, const std::string& name, int index, const map (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::const_iterator b = strip_buttons.begin(); b != strip_buttons.end(); ++b) { Button* bb = dynamic_cast (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 - retval << _surface->sysex_hdr(); + 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; + } + } + } - // code for display - retval << 0x12; // 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 << ' '; } diff --git a/libs/surfaces/mackie/strip.h b/libs/surfaces/mackie/strip.h index 18bd833655..dfa19b75db 100644 --- a/libs/surfaces/mackie/strip.h +++ b/libs/surfaces/mackie/strip.h @@ -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 _stripable; diff --git a/libs/surfaces/mackie/surface.cc b/libs/surfaces/mackie/surface.cc index 891856308f..b5a06b636d 100644 --- a/libs/surfaces/mackie/surface.cc +++ b/libs/surfaces/mackie/surface.cc @@ -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 @@ -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 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()); } @@ -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::factory (*this, _mcp.device_info().strip_cnt(), "master", *master_group)); + _master_fader = dynamic_cast (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::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 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 (); } diff --git a/libs/surfaces/mackie/surface.h b/libs/surfaces/mackie/surface.h index 412b05b8d6..e3b7c5dc33 100644 --- a/libs/surfaces/mackie/surface.h +++ b/libs/surfaces/mackie/surface.h @@ -24,6 +24,7 @@ #include +#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 _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, diff --git a/share/mcp/qcon+qex.device b/share/mcp/qcon+qex.device index 67f02196c6..3410612058 100644 --- a/share/mcp/qcon+qex.device +++ b/share/mcp/qcon+qex.device @@ -1,11 +1,13 @@ + - + + @@ -13,4 +15,6 @@ + + diff --git a/share/mcp/qcon.device b/share/mcp/qcon.device index c5a8034551..0bfd4b3a1d 100644 --- a/share/mcp/qcon.device +++ b/share/mcp/qcon.device @@ -1,11 +1,13 @@ + - + + @@ -13,4 +15,6 @@ + + diff --git a/share/mcp/qcon_g2+g2ex.device b/share/mcp/qcon_g2+g2ex.device new file mode 100644 index 0000000000..32cfb1ec47 --- /dev/null +++ b/share/mcp/qcon_g2+g2ex.device @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/share/mcp/qcon_g2.device b/share/mcp/qcon_g2.device new file mode 100644 index 0000000000..9a4015a38e --- /dev/null +++ b/share/mcp/qcon_g2.device @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/share/mcp/qcon_g2ex+g2.device b/share/mcp/qcon_g2ex+g2.device new file mode 100644 index 0000000000..248b7e36a7 --- /dev/null +++ b/share/mcp/qcon_g2ex+g2.device @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/share/mcp/qex+qcon.device b/share/mcp/qex+qcon.device index b7695781a5..2d24b53daf 100644 --- a/share/mcp/qex+qcon.device +++ b/share/mcp/qex+qcon.device @@ -1,11 +1,13 @@ + - + + @@ -13,4 +15,6 @@ + +