diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2012-04-10 14:27:44 +0000 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2012-04-10 14:27:44 +0000 |
commit | 5ace191bff634c0b09eca5e69065afcb7d0cb183 (patch) | |
tree | 26c7a337e87c2f3e419923016342b1b1b06dbcd6 /libs/surfaces/mackie | |
parent | ac7ade93bda207d60a5276f8fea5a1f01567095b (diff) |
drastic, fundamental redesign of MCP code
git-svn-id: svn://localhost/ardour2/branches/3.0@11861 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/surfaces/mackie')
37 files changed, 1362 insertions, 2254 deletions
diff --git a/libs/surfaces/mackie/bcf_surface.cc b/libs/surfaces/mackie/bcf_surface.cc deleted file mode 100644 index 756e2139c5..0000000000 --- a/libs/surfaces/mackie/bcf_surface.cc +++ /dev/null @@ -1,46 +0,0 @@ -#include <cmath> - -#include "bcf_surface.h" -#include "controls.h" -#include "mackie_midi_builder.h" -#include "surface_port.h" -#include "jog.h" -#include "pot.h" - -using namespace Mackie; - -void -BcfSurface::display_bank_start (SurfacePort & port, MackieMidiBuilder & builder, uint32_t current_bank) -{ - if (current_bank == 0) { - // send Ar. to 2-char display on the master - port.write (builder.two_char_display ("Ar", "..")); - } else { - // write the current first remote_id to the 2-char display - port.write (builder.two_char_display (current_bank)); - } -} - -void -BcfSurface::zero_all (SurfacePort & port, MackieMidiBuilder & builder) -{ - // clear 2-char display - port.write (builder.two_char_display ("LC")); - - // and the led ring for the master strip - blank_jog_ring (port, builder); -} - -void -BcfSurface::blank_jog_ring (SurfacePort & port, MackieMidiBuilder & builder) -{ - Control & control = *controls_by_name["jog"]; - port.write (builder.build_led_ring (dynamic_cast<Pot &> (control), off)); -} - -float -BcfSurface::scaled_delta (const ControlState & state, float current_speed) -{ - return state.sign * (std::pow (float(state.ticks + 1), 2) + current_speed) / 100.0; -} - diff --git a/libs/surfaces/mackie/bcf_surface.h b/libs/surfaces/mackie/bcf_surface.h deleted file mode 100644 index 47ce8a1216..0000000000 --- a/libs/surfaces/mackie/bcf_surface.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef mackie_surface_bcf_h -#define mackie_surface_bcf_h -/* - Initially generated by scripts/generate-surface.rb -*/ - -#include "surface.h" - -namespace Mackie -{ - -class MackieButtonHandler; - -class BcfSurface : public Surface -{ -public: - BcfSurface (uint32_t max_strips) : Surface (max_strips, 7) {} - - virtual void display_bank_start( SurfacePort & port, MackieMidiBuilder & builder, uint32_t current_bank ); - virtual void zero_all( SurfacePort & port, MackieMidiBuilder & builder ); - virtual void blank_jog_ring( SurfacePort & port, MackieMidiBuilder & builder ); - virtual bool has_timecode_display() const { return false; } - - virtual float scrub_scaling_factor() { return 50.0; } - virtual float scaled_delta( const ControlState & state, float current_speed ); - -}; - -} - -#endif diff --git a/libs/surfaces/mackie/button.cc b/libs/surfaces/mackie/button.cc index 5312f1ae86..714294ed7d 100644 --- a/libs/surfaces/mackie/button.cc +++ b/libs/surfaces/mackie/button.cc @@ -24,9 +24,9 @@ using namespace Mackie; Control* -Button::factory (Surface& surface, int id, int ordinal, const char* name, Group& group) +Button::factory (Surface& surface, int id, const char* name, Group& group) { - Button* b = new Button (id, ordinal, name, group); + Button* b = new Button (id, name, group); surface.buttons[id] = b; surface.controls.push_back (b); group.add (*b); diff --git a/libs/surfaces/mackie/button.h b/libs/surfaces/mackie/button.h index 97cd1e309f..2a4dfe8496 100644 --- a/libs/surfaces/mackie/button.h +++ b/libs/surfaces/mackie/button.h @@ -97,15 +97,15 @@ public: ButtonUserB = 0x67, }; - Button (int id, int ordinal, std::string name, Group & group) - : Control (id, ordinal, name, group) - , _led (id, ordinal, name + "_led", group) {} + Button (int id, std::string name, Group & group) + : Control (id, name, group) + , _led (id, name + "_led", group) {} virtual const Led & led() const { return _led; } virtual type_t type() const { return type_button; }; - static Control* factory (Surface&, int id, int ordinal, const char*, Group&); + static Control* factory (Surface&, int id, const char*, Group&); private: Led _led; diff --git a/libs/surfaces/mackie/controls.cc b/libs/surfaces/mackie/controls.cc index 7e0a98a007..745da21969 100644 --- a/libs/surfaces/mackie/controls.cc +++ b/libs/surfaces/mackie/controls.cc @@ -43,9 +43,8 @@ void Group::add (Control& control) _controls.push_back (&control); } -Control::Control (int id, int ordinal, std::string name, Group & group) +Control::Control (int id, std::string name, Group & group) : _id (id) - , _ordinal (ordinal) , _name (name) , _group (group) , _in_use (false) @@ -82,8 +81,6 @@ ostream & Mackie::operator << (ostream & os, const Mackie::Control & control) os << ", "; os << "raw_id: " << "0x" << setw(2) << setfill('0') << hex << control.raw_id() << setfill(' '); os << ", "; - os << "ordinal: " << dec << control.ordinal(); - os << ", "; os << "group: " << control.group().name(); os << " }"; @@ -91,9 +88,9 @@ ostream & Mackie::operator << (ostream & os, const Mackie::Control & control) } Control* -Pot::factory (Surface& surface, int id, int ordinal, const char* name, Group& group) +Pot::factory (Surface& surface, int id, const char* name, Group& group) { - Pot* p = new Pot (id, ordinal, name, group); + Pot* p = new Pot (id, name, group); surface.pots[id] = p; surface.controls.push_back (p); group.add (*p); @@ -101,9 +98,9 @@ Pot::factory (Surface& surface, int id, int ordinal, const char* name, Group& gr } Control* -Led::factory (Surface& surface, int id, int ordinal, const char* name, Group& group) +Led::factory (Surface& surface, int id, const char* name, Group& group) { - Led* l = new Led (id, ordinal, name, group); + Led* l = new Led (id, name, group); surface.leds[id] = l; surface.controls.push_back (l); group.add (*l); @@ -111,9 +108,9 @@ Led::factory (Surface& surface, int id, int ordinal, const char* name, Group& gr } Control* -Jog::factory (Surface& surface, int id, int ordinal, const char* name, Group& group) +Jog::factory (Surface& surface, int id, const char* name, Group& group) { - Jog* j = new Jog (id, ordinal, name, group); + Jog* j = new Jog (id, name, group); surface.controls.push_back (j); surface.controls_by_name["jog"] = j; group.add (*j); diff --git a/libs/surfaces/mackie/controls.h b/libs/surfaces/mackie/controls.h index 81923f23c3..ca972ed5e0 100644 --- a/libs/surfaces/mackie/controls.h +++ b/libs/surfaces/mackie/controls.h @@ -62,7 +62,7 @@ public: meter_base_id = 0xd0, }; - Control (int id, int ordinal, std::string name, Group& group); + Control (int id, std::string name, Group& group); virtual ~Control() {} virtual const Led & led() const { throw MackieControlException ("no led available"); } @@ -76,15 +76,8 @@ public: /// unique within the control type. int raw_id() const { return _id; } - /* this identifies a given control within its MCU "bank of 8" - */ - int control_id() const { return _id % 8; } - - /// The 1-based number of the control - int ordinal() const { return _ordinal; } - const std::string & name() const { return _name; } - const Group & group() const { return _group; } + Group & group() const { return _group; } virtual bool accepts_feedback() const { return true; } virtual type_t type() const = 0; @@ -105,7 +98,6 @@ public: private: int _id; - int _ordinal; std::string _name; Group& _group; bool _in_use; diff --git a/libs/surfaces/mackie/dummy_port.cc b/libs/surfaces/mackie/dummy_port.cc deleted file mode 100644 index 7654f8f987..0000000000 --- a/libs/surfaces/mackie/dummy_port.cc +++ /dev/null @@ -1,58 +0,0 @@ -#include "dummy_port.h" - -#include "midi_byte_array.h" - -#include <midi++/port.h> -#include <midi++/types.h> - -#include <iostream> - -using namespace Mackie; -using namespace std; - -DummyPort::DummyPort() -{ -} - -DummyPort::~DummyPort() -{ -} - - -void DummyPort::open() -{ - cout << "DummyPort::open" << endl; -} - - -void DummyPort::close() -{ - cout << "DummyPort::close" << endl; -} - - -MidiByteArray DummyPort::read() -{ - cout << "DummyPort::read" << endl; - return MidiByteArray(); -} - - -void DummyPort::write( const MidiByteArray & mba ) -{ - cout << "DummyPort::write " << mba << endl; -} - -MidiByteArray empty_midi_byte_array; - -const MidiByteArray & DummyPort::sysex_hdr() const -{ - cout << "DummyPort::sysex_hdr" << endl; - return empty_midi_byte_array; -} - -int DummyPort::strips() const -{ - cout << "DummyPort::strips" << endl; - return 0; -} diff --git a/libs/surfaces/mackie/dummy_port.h b/libs/surfaces/mackie/dummy_port.h deleted file mode 100644 index 056e30fa0d..0000000000 --- a/libs/surfaces/mackie/dummy_port.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2008 John Anderson - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -#ifndef dummy_port_h -#define dummy_port_h - -#include "surface_port.h" - -#include "midi_byte_array.h" - -namespace MIDI { - class Port; -} - -namespace Mackie -{ - -/** - A Dummy Port, to catch things that shouldn't be sent. -*/ -class DummyPort : public SurfacePort -{ -public: - DummyPort(); - virtual ~DummyPort(); - - // when this is successful, active() should return true - virtual void open(); - - // subclasses should call this before doing their own close - virtual 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 - virtual MidiByteArray read(); - - /// an easier way to output bytes via midi - virtual void write( const MidiByteArray & ); - - virtual const MidiByteArray & sysex_hdr() const; - virtual int strips() const; - -}; - -} - -#endif diff --git a/libs/surfaces/mackie/fader.cc b/libs/surfaces/mackie/fader.cc index a7283f42d1..80928d85eb 100644 --- a/libs/surfaces/mackie/fader.cc +++ b/libs/surfaces/mackie/fader.cc @@ -24,11 +24,9 @@ using namespace Mackie; Control* -Fader::factory (Surface& surface, int id, int ordinal, const char* name, Group& group) +Fader::factory (Surface& surface, int id, const char* name, Group& group) { - Fader* f = new Fader (id, ordinal, name, group); - - std::cerr << "Registering fader " << id << " ord " << ordinal << std::endl; + Fader* f = new Fader (id, name, group); surface.faders[id] = f; surface.controls.push_back (f); diff --git a/libs/surfaces/mackie/fader.h b/libs/surfaces/mackie/fader.h index b8c72b8753..7c64016826 100644 --- a/libs/surfaces/mackie/fader.h +++ b/libs/surfaces/mackie/fader.h @@ -7,15 +7,15 @@ namespace Mackie { class Fader : public Control { -public: - Fader (int id, int ordinal, std::string name, Group & group) - : Control (id, ordinal, name, group) + public: + Fader (int id, std::string name, Group & group) + : Control (id, name, group) { } virtual type_t type() const { return type_fader; } - static Control* factory (Surface&, int id, int ordinal, const char*, Group&); + static Control* factory (Surface&, int id, const char*, Group&); }; } diff --git a/libs/surfaces/mackie/jog.h b/libs/surfaces/mackie/jog.h index ec3517348b..c769319766 100644 --- a/libs/surfaces/mackie/jog.h +++ b/libs/surfaces/mackie/jog.h @@ -28,14 +28,14 @@ namespace Mackie { class Jog : public Pot { public: - Jog (int id, int ordinal, std::string name, Group & group) - : Pot (id, ordinal, name, group) + Jog (int id, std::string name, Group & group) + : Pot (id, name, group) { } virtual bool is_jog() const { return true; } - static Control* factory (Surface&, int id, int ordinal, const char*, Group&); + static Control* factory (Surface&, int id, const char*, Group&); }; } diff --git a/libs/surfaces/mackie/led.h b/libs/surfaces/mackie/led.h index 3577e6f0e4..b2bc29ec5f 100644 --- a/libs/surfaces/mackie/led.h +++ b/libs/surfaces/mackie/led.h @@ -27,8 +27,8 @@ namespace Mackie { class Led : public Control { public: - Led (int id, int ordinal, std::string name, Group & group) - : Control (id, ordinal, name, group) + Led (int id, std::string name, Group & group) + : Control (id, name, group) { } @@ -36,7 +36,7 @@ public: virtual type_t type() const { return type_led; } - static Control* factory (Surface&, int id, int ordinal, const char*, Group&); + static Control* factory (Surface&, int id, const char*, Group&); }; } diff --git a/libs/surfaces/mackie/ledring.h b/libs/surfaces/mackie/ledring.h index 3ab95f211a..284c2d26a3 100644 --- a/libs/surfaces/mackie/ledring.h +++ b/libs/surfaces/mackie/ledring.h @@ -28,8 +28,8 @@ namespace Mackie { class LedRing : public Led { public: - LedRing (int id, int ordinal, std::string name, Group & group) - : Led (id, ordinal, name, group) + LedRing (int id, std::string name, Group & group) + : Led (id, name, group) { } diff --git a/libs/surfaces/mackie/mackie_control_protocol.cc b/libs/surfaces/mackie/mackie_control_protocol.cc index 96de6b222d..40f62ba680 100644 --- a/libs/surfaces/mackie/mackie_control_protocol.cc +++ b/libs/surfaces/mackie/mackie_control_protocol.cc @@ -34,7 +34,6 @@ #include "midi++/types.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "pbd/pthread_utils.h" #include "pbd/error.h" #include "pbd/memento_command.h" @@ -57,12 +56,9 @@ #include "midi_byte_array.h" #include "mackie_control_exception.h" -#include "route_signal.h" #include "mackie_midi_builder.h" #include "surface_port.h" #include "surface.h" -#include "bcf_surface.h" -#include "mackie_surface.h" #include "strip.h" #include "control_group.h" @@ -80,27 +76,26 @@ using namespace PBD; #include "pbd/abstract_ui.cc" // instantiate template -#define NUCLEUS_DEBUG 1 - -MackieMidiBuilder builder; - #define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */ #define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__)) extern PBD::EventLoop::InvalidationRecord* __invalidator (sigc::trackable& trackable, const char*, int); -#define invalidator(x) __invalidator ((x), __FILE__, __LINE__) +#define invalidator(x) __invalidator (*(MidiControlUI::instance()), __FILE__, __LINE__) const int MackieControlProtocol::MODIFIER_OPTION = 0x1; const int MackieControlProtocol::MODIFIER_CONTROL = 0x2; const int MackieControlProtocol::MODIFIER_SHIFT = 0x3; const int MackieControlProtocol::MODIFIER_CMDALT = 0x4; +bool MackieControlProtocol::probe() +{ + return true; +} + MackieControlProtocol::MackieControlProtocol (Session& session) : ControlProtocol (session, X_("Mackie"), MidiControlUI::instance()) , AbstractUI<MackieControlUIRequest> ("mackie") , _current_initial_bank (0) - , _surface (0) - , _jog_wheel (*this) , _timecode_type (ARDOUR::AnyTime::BBT) , _input_bundle (new ARDOUR::Bundle (_("Mackie Control In"), true)) , _output_bundle (new ARDOUR::Bundle (_("Mackie Control Out"), false)) @@ -143,35 +138,6 @@ MackieControlProtocol::thread_init () ARDOUR::SessionEvent::create_per_thread_pool (X_("MackieControl"), 128); } -Mackie::Surface& -MackieControlProtocol::surface() -{ - if (_surface == 0) { - throw MackieControlException ("_surface is 0 in MackieControlProtocol::surface"); - } - return *_surface; -} - -const Mackie::SurfacePort& -MackieControlProtocol::mcu_port() const -{ - if (_ports.size() < 1) { - return _dummy_port; - } else { - return dynamic_cast<const MackiePort &> (*_ports[0]); - } -} - -Mackie::SurfacePort& -MackieControlProtocol::mcu_port() -{ - if (_ports.size() < 1) { - return _dummy_port; - } else { - return dynamic_cast<MackiePort &> (*_ports[0]); - } -} - // go to the previous track. // Assume that get_sorted_routes().size() > route_table.size() void @@ -189,45 +155,12 @@ void MackieControlProtocol::next_track() { Sorted sorted = get_sorted_routes(); - if (_current_initial_bank + route_table.size() < sorted.size()) { + if (_current_initial_bank + n_strips() < sorted.size()) { session->set_dirty(); switch_banks (_current_initial_bank + 1); } } -void -MackieControlProtocol::clear_route_signals() -{ - Glib::Mutex::Lock lm (route_signals_lock); - - for (RouteSignals::iterator it = route_signals.begin(); it != route_signals.end(); ++it) { - delete *it; - } - - route_signals.clear(); -} - -// return the port for a given id - 0 based -// throws an exception if no port found -MackiePort& -MackieControlProtocol::port_for_id (uint32_t index) -{ - uint32_t current_max = 0; - - for (MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it) { - current_max += (*it)->strips(); - if (index < current_max) { - return **it; - } - } - - // oops - no matching port - ostringstream os; - os << "No port for index " << index; - cerr << "No port for index " << index << endl; - throw MackieControlException (os.str()); -} - // predicate for sort call in get_sorted_routes struct RouteByRemoteId { @@ -282,19 +215,41 @@ MackieControlProtocol::get_sorted_routes() void MackieControlProtocol::refresh_current_bank() { - switch_banks (_current_initial_bank); + switch_banks (_current_initial_bank, true); +} + +uint32_t +MackieControlProtocol::n_strips() const +{ + uint32_t strip_count = 0; + + for (Surfaces::const_iterator si = surfaces.begin(); si != surfaces.end(); ++si) { + if ((*si)->active()) { + strip_count += (*si)->n_strips (); + } + } + + return strip_count; } void -MackieControlProtocol::switch_banks (int initial) +MackieControlProtocol::switch_banks (uint32_t initial, bool force) { - // DON'T prevent bank switch if initial == _current_initial_bank - // because then this method can't be used as a refresh + if (initial == _current_initial_bank && !force) { + return; + } Sorted sorted = get_sorted_routes(); - int delta = sorted.size() - route_table.size(); + uint32_t strip_cnt = n_strips(); + + if (sorted.size() <= strip_cnt) { + /* no banking */ + return; + } + + uint32_t delta = sorted.size() - strip_cnt; - if (initial < 0 || (delta > 0 && initial > delta)) { + if (delta > 0 && initial > delta) { DEBUG_TRACE (DEBUG::MackieControl, string_compose ("not switching to %1\n", initial)); return; } @@ -302,79 +257,47 @@ MackieControlProtocol::switch_banks (int initial) _current_initial_bank = initial; _current_selected_track = -1; - clear_route_signals(); - - // now set the signals for new routes - if (_current_initial_bank <= sorted.size()) { + for (Surfaces::iterator si = surfaces.begin(); si != surfaces.end(); ++si) { + (*si)->drop_routes (); + } - uint32_t end_pos = min (route_table.size(), sorted.size()); - uint32_t i = 0; - Sorted::iterator it = sorted.begin() + _current_initial_bank; - Sorted::iterator end = sorted.begin() + _current_initial_bank + end_pos; + // Map current bank of routes onto each surface(+strip) - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("switch to %1, %2\n", _current_initial_bank, end_pos)); + if (_current_initial_bank <= sorted.size()) { - route_table.clear (); - set_route_table_size (surface().strips.size()); + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("switch to %1, %2\n", _current_initial_bank, strip_cnt)); // link routes to strips - Glib::Mutex::Lock lm (route_signals_lock); - - for (; it != end && it != sorted.end(); ++it, ++i) { - boost::shared_ptr<Route> route = *it; - - assert (surface().strips[i]); - Strip & strip = *surface().strips[i]; - - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("remote id %1 connecting %2 to %3 with port %4\n", - route->remote_control_id(), route->name(), strip.name(), port_for_id(i))); - set_route_table (i, route); - RouteSignal * rs = new RouteSignal (route, *this, strip, port_for_id(i)); - route_signals.push_back (rs); - rs->notify_all (); - } + Surfaces::iterator si = surfaces.begin(); + uint32_t surface_strip_cnt = (*si)->n_strips(); + uint32_t surface_strip = 0; + + for (Sorted::iterator r = sorted.begin() + _current_initial_bank; r != sorted.end(); ++r) { - // create dead strips if there aren't enough routes to - // fill a bank - for (; i < route_table.size(); ++i) { - Strip & strip = *surface().strips[i]; - // send zero for this strip - MackiePort & port = port_for_id(i); - port.write (builder.zero_strip (port, strip)); - } - } + Strip* strip = (*si)->nth_strip (surface_strip); - // display the current start bank. - surface().display_bank_start (mcu_port(), builder, _current_initial_bank); -} + if (strip) { + strip->set_route (*r); + } -void -MackieControlProtocol::zero_all() -{ - // TODO turn off Timecode displays + if (surface_strip == surface_strip_cnt) { - // zero all strips - for (Surface::Strips::iterator it = surface().strips.begin(); it != surface().strips.end(); ++it) { - MackiePort & port = port_for_id ((*it)->index()); - port.write (builder.zero_strip (port, **it)); - } + /* move to next surface */ - // and the master strip - mcu_port().write (builder.zero_strip (dynamic_cast<MackiePort&> (mcu_port()), master_strip())); + surface_strip = 0; + ++si; - // turn off global buttons and leds - // global buttons are only ever on mcu_port, so we don't have - // to figure out which port. - for (Surface::Controls::iterator it = surface().controls.begin(); it != surface().controls.end(); ++it) { - Control & control = **it; - if (!control.group().is_strip() && control.accepts_feedback()) { - mcu_port().write (builder.zero_control (control)); + if (si == surfaces.end()) { + break; + } + surface_strip_cnt += (*si)->n_strips(); + } } } - // any hardware-specific stuff - surface().zero_all (mcu_port(), builder); + // display the current start bank. + surfaces.front()->display_bank_start (_current_initial_bank); } int @@ -386,51 +309,20 @@ MackieControlProtocol::set_active (bool yn) try { - // the reason for the locking and unlocking is that - // glibmm can't do a condition wait on a RecMutex if (yn) { - // TODO what happens if this fails half way? - // start an event loop - - BaseUI::run (); - - // create MackiePorts - { - Glib::Mutex::Lock lock (update_mutex); - create_ports(); - } - - // now initialise MackiePorts - ie exchange sysex messages - for (MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it) { - (*it)->open(); - } - - // wait until all ports are active - // TODO a more sophisticated approach would - // allow things to start up with only an MCU, even if - // extenders were specified but not responding. - for (MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it) { - (*it)->wait_for_init(); - } + create_surfaces (); + connect_session_signals (); - // create surface object. This depends on the ports being - // correctly initialised - initialize_surface(); - connect_session_signals(); - - // yeehah! _active = true; - - // send current control positions to surface - // must come after _active = true otherwise it won't run - update_surface(); + update_surfaces (); - Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (100); // milliseconds + /* set up periodic task for metering and automation + */ + Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (100); // milliseconds periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &MackieControlProtocol::periodic)); - - periodic_timeout->attach (main_loop()->get_context()); + periodic_timeout->attach (MidiControlUI::instance()->main_loop()->get_context()); } else { BaseUI::quit (); @@ -455,84 +347,15 @@ MackieControlProtocol::periodic () return false; } - { - Glib::Mutex::Lock lm (route_signals_lock); - for (RouteSignals::iterator rs = route_signals.begin(); rs != route_signals.end(); ++rs) { - update_automation (**rs); - float dB = const_cast<PeakMeter&> ((*rs)->route()->peak_meter()).peak_power (0); - (*rs)->port().write ((*rs)->strip().meter().update_message (dB)); - } - - // and the master strip - if (master_route_signal != 0) { - update_automation (*master_route_signal); - } + for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) { + (*s)->periodic (); } - + update_timecode_display(); return true; } -bool -MackieControlProtocol::handle_strip_button (SurfacePort & port, Control & control, ButtonState bs, boost::shared_ptr<Route> route) -{ - bool state = false; - - if (bs == press) { - if (control.name() == "recenable") { - state = !route->record_enabled(); - route->set_record_enabled (state, this); - } else if (control.name() == "mute") { - state = !route->muted(); - route->set_mute (state, this); - } else if (control.name() == "solo") { - state = !route->soloed(); - route->set_solo (state, this); - } else if (control.name() == "select") { - Strip* strip = const_cast<Strip*>(dynamic_cast<const Strip*>(&control.group())); - if (strip) { - - if ((uint32_t) strip->index() < route_table.size()) { - boost::shared_ptr<Route> r = route_table[strip->index()]; - - if (_modifier_state == MODIFIER_SHIFT) { - r->gain_control()->set_value (0.0); - } else { - if (r->remote_control_id() == _current_selected_track) { - UnselectTrack (); /* EMIT SIGNAL */ - _current_selected_track = -1; - } else { - SelectByRID (r->remote_control_id()); /* EMIT SIGNAL */ - _current_selected_track = r->remote_control_id();; - } - } - } - } - } else if (control.name() == "vselect") { - // TODO could be used to select different things to apply the pot to? - //state = default_button_press (dynamic_cast<Button&> (control)); - } - } - - if (control.name() == "fader_touch") { - state = bs == press; - Strip* strip = const_cast<Strip*>(dynamic_cast<const Strip*>(&control.group())); - - if (strip) { - strip->gain().set_in_use (state); - - if (ARDOUR::Config->get_mackie_emulation() == "bcf" && state) { - /* BCF faders don't support touch, so add a timeout to reset - their `in_use' state. - */ - add_in_use_timeout (port, strip->gain(), &strip->fader_touch()); - } - } - } - - return state; -} void MackieControlProtocol::update_timecode_beats_led() @@ -556,9 +379,15 @@ MackieControlProtocol::update_timecode_beats_led() void MackieControlProtocol::update_global_button (const string & name, LedState ls) { - if (surface().controls_by_name.find (name) != surface().controls_by_name.end()) { - Button * button = dynamic_cast<Button*> (surface().controls_by_name[name]); - mcu_port().write (builder.build_led (button->led(), ls)); + boost::shared_ptr<Surface> surface = surfaces.front(); + + if (!surface->type() == mcu) { + return; + } + + if (surface->controls_by_name.find (name) != surface->controls_by_name.end()) { + Button * button = dynamic_cast<Button*> (surface->controls_by_name[name]); + surface->write (builder.build_led (button->led(), ls)); } else { DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Button %1 not found\n", name)); } @@ -567,9 +396,15 @@ MackieControlProtocol::update_global_button (const string & name, LedState ls) void MackieControlProtocol::update_global_led (const string & name, LedState ls) { - if (surface().controls_by_name.find (name) != surface().controls_by_name.end()) { - Led * led = dynamic_cast<Led*> (surface().controls_by_name[name]); - mcu_port().write (builder.build_led (*led, ls)); + boost::shared_ptr<Surface> surface = surfaces.front(); + + if (!surface->type() == mcu) { + return; + } + + if (surface->controls_by_name.find (name) != surface->controls_by_name.end()) { + Led * led = dynamic_cast<Led*> (surface->controls_by_name[name]); + surface->write (builder.build_led (*led, ls)); } else { DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Led %1 not found\n", name)); } @@ -577,7 +412,7 @@ MackieControlProtocol::update_global_led (const string & name, LedState ls) // send messages to surface to set controls to correct values void -MackieControlProtocol::update_surface() +MackieControlProtocol::update_surfaces() { if (!_active) { return; @@ -585,22 +420,13 @@ MackieControlProtocol::update_surface() // do the initial bank switch to connect signals // _current_initial_bank is initialised by set_state - switch_banks (_current_initial_bank); - - /* Create a RouteSignal for the master route, if we don't already have one */ - if (!master_route_signal) { - boost::shared_ptr<Route> mr = master_route (); - if (mr) { - master_route_signal = boost::shared_ptr<RouteSignal> (new RouteSignal (mr, *this, master_strip(), mcu_port())); - // update strip from route - master_route_signal->notify_all(); - } - } + switch_banks (_current_initial_bank, true); // sometimes the jog wheel is a pot - surface().blank_jog_ring (mcu_port(), builder); + surfaces.front()->blank_jog_ring (); // update global buttons and displays + notify_record_state_changed(); notify_transport_state_changed(); update_timecode_beats_led(); @@ -631,157 +457,45 @@ MackieControlProtocol::connect_session_signals() } void -MackieControlProtocol::add_port (MIDI::Port & midi_input_port, MIDI::Port & midi_output_port, int number, MackiePort::port_type_t port_type) -{ - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("add port %1 %2\n", midi_input_port.name(), midi_output_port.name())); - - MackiePort * sport = new MackiePort (*this, midi_input_port, midi_output_port, number, port_type); - _ports.push_back (sport); - - sport->init_event.connect_same_thread (port_connections, boost::bind (&MackieControlProtocol::handle_port_init, this, sport)); - sport->active_event.connect_same_thread (port_connections, boost::bind (&MackieControlProtocol::handle_port_active, this, sport)); - sport->inactive_event.connect_same_thread (port_connections, boost::bind (&MackieControlProtocol::handle_port_inactive, this, sport)); - - _input_bundle->add_channel ( - midi_input_port.name(), - ARDOUR::DataType::MIDI, - session->engine().make_port_name_non_relative (midi_input_port.name()) - ); - - _output_bundle->add_channel ( - midi_output_port.name(), - ARDOUR::DataType::MIDI, - session->engine().make_port_name_non_relative (midi_output_port.name()) - ); -} - -void -MackieControlProtocol::create_ports() +MackieControlProtocol::create_surfaces () { - MIDI::Manager * mm = MIDI::Manager::instance(); - MIDI::Port * midi_input_port = mm->add_port ( - new MIDI::Port (string_compose (_("%1 in"), default_port_name), MIDI::Port::IsInput, session->engine().jack()) - ); - MIDI::Port * midi_output_port = mm->add_port ( - new MIDI::Port (string_compose (_("%1 out"), default_port_name), MIDI::Port::IsOutput, session->engine().jack()) - ); + string device_name = "mcu"; + surface_type_t stype = mcu; - /* Create main port */ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Create %1 surfaces\n", + 1 + ARDOUR::Config->get_mackie_extenders())); - if (!midi_input_port->ok() || !midi_output_port->ok()) { - ostringstream os; - os << _("Mackie control MIDI ports could not be created; Mackie control disabled"); - error << os.str() << endmsg; - throw MackieControlException (os.str()); - } + for (uint32_t n = 0; n < 1 + ARDOUR::Config->get_mackie_extenders(); ++n) { - add_port (*midi_input_port, *midi_output_port, 0, MackiePort::mcu); - - /* Create extender ports */ + boost::shared_ptr<Surface> surface (new Surface (*this, session->engine().jack(), device_name, n, stype)); + surfaces.push_back (surface); + + device_name = "mcu_xt"; + stype = ext; - for (uint32_t index = 1; index <= Config->get_mackie_extenders(); ++index) { - MIDI::Port * midi_input_port = mm->add_port ( - new MIDI::Port (string_compose (_("mcu_xt_%1 in"), index), MIDI::Port::IsInput, session->engine().jack()) + _input_bundle->add_channel ( + surface->port().input_port().name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (surface->port().input_port().name()) ); - MIDI::Port * midi_output_port = mm->add_port ( - new MIDI::Port (string_compose (_("mcu_xt_%1 out"), index), MIDI::Port::IsOutput, session->engine().jack()) + + _output_bundle->add_channel ( + surface->port().output_port().name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (surface->port().output_port().name()) ); - if (midi_input_port->ok() && midi_output_port->ok()) { - add_port (*midi_input_port, *midi_output_port, index, MackiePort::ext); - } - } -} - -boost::shared_ptr<Route> -MackieControlProtocol::master_route() -{ - return session->master_out (); -} - -Strip& -MackieControlProtocol::master_strip() -{ - return dynamic_cast<Strip&> (*surface().groups["master"]); -} - -void -MackieControlProtocol::initialize_surface() -{ - // set up the route table - int strips = 0; - for (MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it) { - strips += (*it)->strips(); - } - - set_route_table_size (strips); - - // TODO same as code in mackie_port.cc - string emulation = ARDOUR::Config->get_mackie_emulation(); - if (emulation == "bcf") { - _surface = new BcfSurface (strips); - } else if (emulation == "mcu") { - _surface = new MackieSurface (strips); - } else { - ostringstream os; - os << "no Surface class found for emulation: " << emulation; - throw MackieControlException (os.str()); } - - _surface->init(); } void MackieControlProtocol::close() { - - // must be before other shutdown otherwise polling loop - // calls methods on objects that are deleted - port_connections.drop_connections (); session_connections.drop_connections (); route_connections.drop_connections (); periodic_connection.disconnect (); - if (_surface != 0) { - // These will fail if the port has gone away. - // So catch the exception and do the rest of the - // close afterwards - // because the bcf doesn't respond to the next 3 sysex messages - try { - zero_all(); - } - - catch (exception & e) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieControlProtocol::close caught exception: %1\n", e.what())); - } - - for (MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it) { - try { - MackiePort & port = **it; - // faders to minimum - port.write_sysex (0x61); - // All LEDs off - port.write_sysex (0x62); - // Reset (reboot into offline mode) - port.write_sysex (0x63); - } - catch (exception & e) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieControlProtocol::close caught exception: %1\n", e.what())); - } - } - - // disconnect routes from strips - clear_route_signals(); - delete _surface; - _surface = 0; - } - - // shut down MackiePorts - for (MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it) { - delete *it; - } - - _ports.clear(); + surfaces.clear (); } XMLNode& @@ -828,120 +542,6 @@ MackieControlProtocol::set_state (const XMLNode & node, int /*version*/) return retval; } -void -MackieControlProtocol::handle_control_event (SurfacePort & port, Control & control, const ControlState & state) -{ - // find the route for the control, if there is one - boost::shared_ptr<Route> route; - - if (control.group().is_strip()) { - if (control.group().is_master()) { - DEBUG_TRACE (DEBUG::MackieControl, "master strip control event\n"); - route = master_route(); - } else { - uint32_t index = control.ordinal() - 1 + (port.number() * port.strips()); - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip control event, index = %1, rt size = %2\n", - index, route_table.size())); - if (index < route_table.size()) { - route = route_table[index]; - if (route) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("modifying %1\n", route->name())); - } else { - DEBUG_TRACE (DEBUG::MackieControl, "no route found!\n"); - } - } else { - cerr << "Warning: index is " << index << " which is not in the route table, size: " << route_table.size() << endl; - DEBUG_TRACE (DEBUG::MackieControl, "illegal route index found!\n"); - } - } - } - - // This handles control element events from the surface - // the state of the controls on the surface is usually updated - // from UI events. - switch (control.type()) { - case Control::type_fader: - // find the route in the route table for the id - // if the route isn't available, skip it - // at which point the fader should just reset itself - if (route != 0) - { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("fader to %1\n", state.pos)); - - route->gain_control()->set_value (slider_position_to_gain (state.pos)); - - if (ARDOUR::Config->get_mackie_emulation() == "bcf") { - /* reset the timeout while we're still moving the fader */ - add_in_use_timeout (port, control, control.in_use_touch_control); - } - - // must echo bytes back to slider now, because - // the notifier only works if the fader is not being - // touched. Which it is if we're getting input. - port.write (builder.build_fader ((Fader&)control, state.pos)); - } - break; - - case Control::type_button: - if (control.group().is_strip()) { - // strips - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip button %1\n", control.id())); - if (route != 0) { - handle_strip_button (port, control, state.button_state, route); - } else { - // no route so always switch the light off - // because no signals will be emitted by a non-route - port.write (builder.build_led (control.led(), off)); - } - } else if (control.group().is_master()) { - // master fader touch - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("master strip button %1\n", control.id())); - if (route != 0) { - handle_strip_button (port, control, state.button_state, route); - } - } else { - // handle all non-strip buttons - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("global button %1\n", control.id())); - handle_button_event (dynamic_cast<Button&>(control), state.button_state); - - } - break; - - // pot (jog wheel, external control) - case Control::type_pot: - if (control.group().is_strip()) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip pot %1\n", control.id())); - if (route) { - boost::shared_ptr<Panner> panner = route->panner_shell()->panner(); - // pan for mono input routes, or stereo linked panners - if (panner) { - double p = panner->position (); - - // calculate new value, and adjust - p += state.delta * state.sign; - p = min (1.0, p); - p = max (0.0, p); - panner->set_position (p); - } - } else { - // it's a pot for an umnapped route, so turn all the lights off - port.write (builder.build_led_ring (dynamic_cast<Pot &> (control), off)); - } - } else { - if (control.is_jog()) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Jog wheel moved %1\n", state.ticks)); - _jog_wheel.jog_event (port, control, state); - } else { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("External controller moved %1\n", state.ticks)); - cout << "external controller" << state.ticks * state.sign << endl; - } - } - break; - - default: - cout << "Control::type not handled: " << control.type() << endl; - } -} ///////////////////////////////////////////////// // handlers for Route signals @@ -950,156 +550,7 @@ MackieControlProtocol::handle_control_event (SurfacePort & port, Control & contr // from Route, but they're also used in polling for automation ///////////////////////////////////////////////// -void -MackieControlProtocol::notify_solo_changed (RouteSignal * route_signal) -{ - try { - Button & button = route_signal->strip().solo(); - route_signal->port().write (builder.build_led (button, route_signal->route()->soloed())); - } - catch (exception & e) { - cout << e.what() << endl; - } -} - -void -MackieControlProtocol::notify_mute_changed (RouteSignal * route_signal) -{ - try { - Button & button = route_signal->strip().mute(); - route_signal->port().write (builder.build_led (button, route_signal->route()->muted())); - } - catch (exception & e) { - cout << e.what() << endl; - } -} - -void -MackieControlProtocol::notify_record_enable_changed (RouteSignal * route_signal) -{ - try { - Button & button = route_signal->strip().recenable(); - route_signal->port().write (builder.build_led (button, route_signal->route()->record_enabled())); - } - catch (exception & e) { - cout << e.what() << endl; - } -} - -void MackieControlProtocol::notify_active_changed (RouteSignal *) -{ - try { - DEBUG_TRACE (DEBUG::MackieControl, "MackieControlProtocol::notify_active_changed\n"); - refresh_current_bank(); - } - catch (exception & e) { - cout << e.what() << endl; - } -} - -void -MackieControlProtocol::notify_gain_changed (RouteSignal * route_signal, bool force_update) -{ - try { - Fader & fader = route_signal->strip().gain(); - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("route %1 gain change, update fader %2 on port %3\n", - route_signal->route()->name(), - fader.raw_id(), - route_signal->port().output_port().name())); - if (!fader.in_use()) { - float gain_value = gain_to_slider_position (route_signal->route()->gain_control()->get_value()); - // check that something has actually changed - if (force_update || gain_value != route_signal->last_gain_written()) { - route_signal->port().write (builder.build_fader (fader, gain_value)); - route_signal->last_gain_written (gain_value); - } - } - } - catch (exception & e) { - cout << e.what() << endl; - } -} - -void -MackieControlProtocol::notify_property_changed (const PropertyChange& what_changed, RouteSignal * route_signal) -{ - if (!what_changed.contains (Properties::name)) { - return; - } - - try { - Strip & strip = route_signal->strip(); - - if (!strip.is_master()) { - string line1; - string fullname = route_signal->route()->name(); - - if (fullname.length() <= 6) { - line1 = fullname; - } else { - line1 = PBD::short_version (fullname, 6); - } - -#ifdef NUCLEUS_DEBUG - cerr << "show strip name from " << fullname << " as " << line1 << endl; -#endif - - SurfacePort & port = route_signal->port(); - port.write (builder.strip_display (port, strip, 0, line1)); - port.write (builder.strip_display_blank (port, strip, 1)); - } - } - catch (exception & e) { - cout << e.what() << endl; - } -} - -void -MackieControlProtocol::notify_panner_changed (RouteSignal * route_signal, bool force_update) -{ - try { - Pot & pot = route_signal->strip().vpot(); - boost::shared_ptr<Panner> panner = route_signal->route()->panner(); - if (panner) { - double pos = panner->position (); - - // cache the MidiByteArray here, because the mackie led control is much lower - // resolution than the panner control. So we save lots of byte - // sends in spite of more work on the comparison - MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, pos), MackieMidiBuilder::midi_pot_mode_dot); - // check that something has actually changed - if (force_update || bytes != route_signal->last_pan_written()) - { - route_signal->port().write (bytes); - route_signal->last_pan_written (bytes); - } - } else { - route_signal->port().write (builder.zero_control (pot)); - } - } - catch (exception & e) { - cout << e.what() << endl; - } -} - // TODO handle plugin automation polling -void -MackieControlProtocol::update_automation (RouteSignal & rs) -{ - ARDOUR::AutoState gain_state = rs.route()->gain_control()->automation_state(); - - if (gain_state == Touch || gain_state == Play) { - notify_gain_changed (&rs, false); - } - - if (rs.route()->panner()) { - ARDOUR::AutoState panner_state = rs.route()->panner()->automation_state(); - if (panner_state == Touch || panner_state == Play) { - notify_panner_changed (&rs, false); - } - } -} - string MackieControlProtocol::format_bbt_timecode (framepos_t now_frame) { @@ -1150,33 +601,34 @@ MackieControlProtocol::format_timecode_timecode (framepos_t now_frame) void MackieControlProtocol::update_timecode_display() { - if (surface().has_timecode_display()) { - // do assignment here so current_frame is fixed - framepos_t current_frame = session->transport_frame(); - string timecode; - - switch (_timecode_type) { - case ARDOUR::AnyTime::BBT: - timecode = format_bbt_timecode (current_frame); - break; - case ARDOUR::AnyTime::Timecode: - timecode = format_timecode_timecode (current_frame); - break; - default: - ostringstream os; - os << "Unknown timecode: " << _timecode_type; - throw runtime_error (os.str()); - } - - // only write the timecode string to the MCU if it's changed - // since last time. This is to reduce midi bandwidth used. - if (timecode != _timecode_last) { - surface().display_timecode (mcu_port(), builder, timecode, _timecode_last); - _timecode_last = timecode; - } + boost::shared_ptr<Surface> surface = surfaces.front(); + + if (surface->type() != mcu || !surface->has_timecode_display()) { + return; } -} + // do assignment here so current_frame is fixed + framepos_t current_frame = session->transport_frame(); + string timecode; + + switch (_timecode_type) { + case ARDOUR::AnyTime::BBT: + timecode = format_bbt_timecode (current_frame); + break; + case ARDOUR::AnyTime::Timecode: + timecode = format_timecode_timecode (current_frame); + break; + default: + return; + } + + // only write the timecode string to the MCU if it's changed + // since last time. This is to reduce midi bandwidth used. + if (timecode != _timecode_last) { + surface->display_timecode (timecode, _timecode_last); + _timecode_last = timecode; + } +} /////////////////////////////////////////// // Session signals @@ -1202,12 +654,7 @@ MackieControlProtocol::notify_route_added (ARDOUR::RouteList & rl) // currently assigned banks are less than the full set of // strips, so activate the new strip now. - { - Glib::Mutex::Lock lm (route_signals_lock); - if (route_signals.size() < route_table.size()) { - refresh_current_bank(); - } - } + refresh_current_bank(); // otherwise route added, but current bank needs no updating @@ -1222,25 +669,24 @@ MackieControlProtocol::notify_route_added (ARDOUR::RouteList & rl) void MackieControlProtocol::notify_solo_active_changed (bool active) { - Button * rude_solo = reinterpret_cast<Button*> (surface().controls_by_name["solo"]); - mcu_port().write (builder.build_led (*rude_solo, active ? flashing : off)); + boost::shared_ptr<Surface> surface = surfaces.front(); + + Button * rude_solo = reinterpret_cast<Button*> (surface->controls_by_name["solo"]); + + if (rude_solo) { + surface->write (builder.build_led (*rude_solo, active ? flashing : off)); + } } void MackieControlProtocol::notify_remote_id_changed() { Sorted sorted = get_sorted_routes(); + uint32_t sz = n_strips(); // if a remote id has been moved off the end, we need to shift // the current bank backwards. - uint32_t sz; - - { - Glib::Mutex::Lock lm (route_signals_lock); - sz = route_signals.size(); - } - if (sorted.size() - _current_initial_bank < sz) { // but don't shift backwards past the zeroth channel switch_banks (max((Sorted::size_type) 0, sorted.size() - sz)); @@ -1258,8 +704,10 @@ void MackieControlProtocol::notify_record_state_changed() { // switch rec button on / off / flashing - Button * rec = reinterpret_cast<Button*> (surface().controls_by_name["record"]); - mcu_port().write (builder.build_led (*rec, record_release (*rec))); + Button * rec = reinterpret_cast<Button*> (surfaces.front()->controls_by_name["record"]); + if (rec) { + surfaces.front()->write (builder.build_led (*rec, record_release (*rec))); + } } void @@ -1273,37 +721,12 @@ MackieControlProtocol::notify_transport_state_changed() _transport_previously_rolling = session->transport_rolling(); // rec is special because it's tristate - Button * rec = reinterpret_cast<Button*> (surface().controls_by_name["record"]); - mcu_port().write (builder.build_led (*rec, record_release (*rec))); -} - - -void -jog_wheel_state_display (JogWheel::State state, SurfacePort & port) -{ - switch (state) { - case JogWheel::zoom: - port.write (builder.two_char_display ("Zm")); - break; - case JogWheel::scroll: - port.write (builder.two_char_display ("Sc")); - break; - case JogWheel::scrub: - port.write (builder.two_char_display ("Sb")); - break; - case JogWheel::shuttle: - port.write (builder.two_char_display ("Sh")); - break; - case JogWheel::speed: - port.write (builder.two_char_display ("Sp")); - break; - case JogWheel::select: - port.write (builder.two_char_display ("Se")); - break; + Button * rec = reinterpret_cast<Button*> (surfaces.front()->controls_by_name["record"]); + if (rec) { + surfaces.front()->write (builder.build_led (*rec, record_release (*rec))); } } - list<boost::shared_ptr<ARDOUR::Bundle> > MackieControlProtocol::bundles () { @@ -1324,20 +747,12 @@ MackieControlProtocol::port_connected_or_disconnected (string a, string b, bool return; } - MackiePorts::const_iterator i = _ports.begin(); - while (i != _ports.end()) { - - string const n = AudioEngine::instance()->make_port_name_non_relative ((*i)->output_port().name ()); - + for (Surfaces::iterator s = surfaces.begin(); s != surfaces.end(); ++s) { + string const n = AudioEngine::instance()->make_port_name_non_relative ((*s)->port().output_port().name ()); if (a == n || b == n) { - break; + update_surfaces (); + return; } - - ++i; - } - - if (i != _ports.end ()) { - update_surface (); } } @@ -1367,19 +782,19 @@ MackieControlProtocol::stop () * @param touch_control a touch control to emit an event for, or 0. */ void -MackieControlProtocol::add_in_use_timeout (SurfacePort& port, Control& in_use_control, Control* touch_control) +MackieControlProtocol::add_in_use_timeout (Surface& surface, Control& in_use_control, Control* touch_control) { Glib::RefPtr<Glib::TimeoutSource> timeout (Glib::TimeoutSource::create (250)); // milliseconds in_use_control.in_use_connection.disconnect (); in_use_control.in_use_connection = timeout->connect ( - sigc::bind (sigc::mem_fun (*this, &MackieControlProtocol::control_in_use_timeout), &port, &in_use_control, touch_control)); + sigc::bind (sigc::mem_fun (*this, &MackieControlProtocol::control_in_use_timeout), &surface, &in_use_control, touch_control)); in_use_control.in_use_touch_control = touch_control; + + timeout->attach (MidiControlUI::instance()->main_loop()->get_context()); - timeout->attach (main_loop()->get_context()); - - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("timeout queued for port %1, control %2 touch control %3\n", - &port, &in_use_control, touch_control));} + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("timeout queued for surface %1, control %2 touch control %3\n", + surface.number(), &in_use_control, touch_control));} /** Handle timeouts to reset in_use for controls that can't * do this by themselves (e.g. pots, and faders without touch support). @@ -1387,17 +802,17 @@ MackieControlProtocol::add_in_use_timeout (SurfacePort& port, Control& in_use_co * @param touch_control a touch control to emit an event for, or 0. */ bool -MackieControlProtocol::control_in_use_timeout (SurfacePort* port, Control* in_use_control, Control* touch_control) +MackieControlProtocol::control_in_use_timeout (Surface* surface, Control* in_use_control, Control* touch_control) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("timeout elapsed for port %1, control %2 touch control %3\n", - port, in_use_control, touch_control)); + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("timeout elapsed for surface %1, control %2 touch control %3\n", + surface->number(), in_use_control, touch_control)); in_use_control->set_in_use (false); if (touch_control) { // empty control_state ControlState control_state; - handle_control_event (*port, *touch_control, control_state); + surface->handle_control_event (*touch_control, control_state); } // only call this method once from the timer @@ -1405,29 +820,18 @@ MackieControlProtocol::control_in_use_timeout (SurfacePort* port, Control* in_us } void -MackieControlProtocol::update_led (Button& button, Mackie::LedState ls) +MackieControlProtocol::update_led (Surface& surface, Button& button, Mackie::LedState ls) { if (ls != none) { - SurfacePort * port = 0; - - if (button.group().is_strip()) { - if (button.group().is_master()) { - port = &mcu_port(); - } else { - port = &port_for_id (dynamic_cast<const Strip&> (button.group()).index()); - } - } else { - port = &mcu_port(); - } - port->write (builder.build_led (button, ls)); + surface.port().write (builder.build_led (button, ls)); } } void -MackieControlProtocol::handle_button_event (Button& button, ButtonState bs) +MackieControlProtocol::handle_button_event (Surface& surface, Button& button, ButtonState bs) { if (bs != press && bs != release) { - update_led (button, none); + update_led (surface, button, none); return; } @@ -1950,5 +1354,22 @@ MackieControlProtocol::handle_button_event (Button& button, ButtonState bs) } - update_led (button, ls); + update_led (surface, button, ls); +} + +void +MackieControlProtocol::select_track (boost::shared_ptr<Route> r) +{ + if (_modifier_state == MODIFIER_SHIFT) { + r->gain_control()->set_value (0.0); + } else { + if (_current_selected_track > 0 && r->remote_control_id() == (uint32_t) _current_selected_track) { + UnselectTrack (); /* EMIT SIGNAL */ + _current_selected_track = -1; + } else { + SelectByRID (r->remote_control_id()); /* EMIT SIGNAL */ + _current_selected_track = r->remote_control_id();; + } + } } + diff --git a/libs/surfaces/mackie/mackie_control_protocol.h b/libs/surfaces/mackie/mackie_control_protocol.h index b7f3a2b30d..0232cd46d7 100644 --- a/libs/surfaces/mackie/mackie_control_protocol.h +++ b/libs/surfaces/mackie/mackie_control_protocol.h @@ -23,22 +23,22 @@ #include <sys/time.h> #include <pthread.h> +#include <boost/smart_ptr.hpp> #include <glibmm/thread.h> #include "pbd/abstract_ui.h" -#include "ardour/types.h" -#include "ardour/midi_ui.h" #include "midi++/types.h" +#include "ardour/types.h" + #include "control_protocol/control_protocol.h" #include "midi_byte_array.h" #include "controls.h" -#include "dummy_port.h" -#include "route_signal.h" #include "mackie_port.h" #include "mackie_jog_wheel.h" +#include "mackie_midi_builder.h" #include "timer.h" namespace MIDI { @@ -92,36 +92,28 @@ class MackieControlProtocol static bool probe(); - Mackie::Surface & surface(); + typedef std::list<boost::shared_ptr<Mackie::Surface> > Surfaces; + Surfaces surfaces; std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles (); + uint32_t n_strips () const; + bool has_editor () const { return true; } void* get_gui () const; void tear_down_gui (); + + void select_track (boost::shared_ptr<ARDOUR::Route> r); - // control events - void handle_control_event (Mackie::SurfacePort & port, Mackie::Control & control, const Mackie::ControlState & state); - void handle_button_event (Mackie::Button& button, Mackie::ButtonState); - - // strip/route related stuff - public: - void notify_solo_changed (Mackie::RouteSignal *); - void notify_mute_changed (Mackie::RouteSignal *); - void notify_record_enable_changed (Mackie::RouteSignal *); - void notify_gain_changed (Mackie::RouteSignal *, bool force_update = true); - void notify_property_changed (const PBD::PropertyChange&, Mackie::RouteSignal *); - void notify_panner_changed (Mackie::RouteSignal *, bool force_update = true); + void handle_button_event (Mackie::Surface&, Mackie::Button& button, Mackie::ButtonState); + void notify_route_added (ARDOUR::RouteList &); - void notify_active_changed (Mackie::RouteSignal *); - void notify_remote_id_changed(); /// rebuild the current bank. Called on route added/removed and /// remote id changed. void refresh_current_bank(); - public: // button-related signals void notify_record_state_changed(); void notify_transport_state_changed(); @@ -134,7 +126,7 @@ class MackieControlProtocol void update_timecode_beats_led(); /// this is called to generate the midi to send in response to a button press. - void update_led(Mackie::Button & button, Mackie::LedState); + void update_led(Mackie::Surface&, Mackie::Button & button, Mackie::LedState); void update_global_button(const std::string & name, Mackie::LedState); void update_global_led(const std::string & name, Mackie::LedState); @@ -280,30 +272,17 @@ class MackieControlProtocol Mackie::LedState fader_touch_press (Mackie::Button &); Mackie::LedState fader_touch_release (Mackie::Button &); - - /// This is the main MCU port, ie not an extender port - /// Only for use by JogWheel - const Mackie::SurfacePort & mcu_port() const; - Mackie::SurfacePort & mcu_port(); ARDOUR::Session & get_session() { return *session; } - void add_in_use_timeout (Mackie::SurfacePort& port, Mackie::Control& in_use_control, Mackie::Control* touch_control); + void add_in_use_timeout (Mackie::Surface& surface, Mackie::Control& in_use_control, Mackie::Control* touch_control); protected: - // create instances of MackiePort, depending on what's found in ardour.rc - void create_ports(); - // shut down the surface void close(); - // create the Surface object, with the correct number - // of strips for the currently connected ports and - // hook up the control event notification - void initialize_surface(); - // This sets up the notifications and sets the // controls to the correct values - void update_surface(); + void update_surfaces(); // connects global (not strip) signals from the Session to here // so the surface can be notified of changes from the other UIs. @@ -320,54 +299,16 @@ class MackieControlProtocol Sorted get_sorted_routes(); // bank switching - void switch_banks(int initial); - void prev_track(); - void next_track(); + void switch_banks (uint32_t first_remote_id, bool force = false); + void prev_track (); + void next_track (); - // delete all RouteSignal objects connecting Routes to Strips - void clear_route_signals(); - - typedef std::vector<Mackie::RouteSignal*> RouteSignals; - RouteSignals route_signals; - Glib::Mutex route_signals_lock; - - // return which of the ports a particular route_table - // index belongs to - Mackie::MackiePort & port_for_id(uint32_t index); - - /** - Handle a button press for the control and return whether - the corresponding light should be on or off. - */ - bool handle_strip_button (Mackie::SurfacePort &, Mackie::Control &, Mackie::ButtonState, boost::shared_ptr<ARDOUR::Route>); - - void add_port (MIDI::Port &, MIDI::Port &, int number, Mackie::MackiePort::port_type_t); - - // called from poll_automation to figure out which automations need to be sent - void update_automation(Mackie::RouteSignal &); - // also called from poll_automation to update timecode display void update_timecode_display(); std::string format_bbt_timecode (ARDOUR::framepos_t now_frame); std::string format_timecode_timecode (ARDOUR::framepos_t now_frame); - /** - notification that the port is about to start it's init sequence. - We must make sure that before this exits, the port is being polled - for new data. - */ - void handle_port_init(Mackie::SurfacePort *); - - /// notification from a MackiePort that it's now active - void handle_port_active(Mackie::SurfacePort *); - - /// notification from a MackiePort that it's now inactive - void handle_port_inactive(Mackie::SurfacePort *); - - boost::shared_ptr<ARDOUR::Route> master_route(); - Mackie::Strip & master_strip(); - void do_request (MackieControlUIRequest*); int stop (); @@ -375,23 +316,13 @@ class MackieControlProtocol private: + void create_surfaces (); void port_connected_or_disconnected (std::string, std::string, bool); - bool control_in_use_timeout (Mackie::SurfacePort*, Mackie::Control *, Mackie::Control *); + bool control_in_use_timeout (Mackie::Surface*, Mackie::Control *, Mackie::Control *); bool periodic(); sigc::connection periodic_connection; - boost::shared_ptr<Mackie::RouteSignal> master_route_signal; - - static const char * default_port_name; - - /// The Midi port(s) connected to the units - typedef std::vector<Mackie::MackiePort*> MackiePorts; - MackiePorts _ports; - - /// Sometimes the real port goes away, and we want to contain the breakage - Mackie::DummyPort _dummy_port; - /// The initial remote_id of the currently switched in bank. uint32_t _current_initial_bank; @@ -403,16 +334,11 @@ class MackieControlProtocol PBD::ScopedConnectionList port_connections; PBD::ScopedConnectionList route_connections; - /// The representation of the physical controls on the surface. - Mackie::Surface * _surface; - bool _transport_previously_rolling; // timer for two quick marker left presses Mackie::Timer _frm_left_last; - Mackie::JogWheel _jog_wheel; - // last written timecode string std::string _timecode_last; @@ -437,6 +363,8 @@ class MackieControlProtocol static const int MODIFIER_CMDALT; int _modifier_state; + + Mackie::MackieMidiBuilder builder; }; #endif // ardour_mackie_control_protocol_h diff --git a/libs/surfaces/mackie/mackie_control_protocol_poll.cc b/libs/surfaces/mackie/mackie_control_protocol_poll.cc index 06aafc5965..66c80c9a8b 100644 --- a/libs/surfaces/mackie/mackie_control_protocol_poll.cc +++ b/libs/surfaces/mackie/mackie_control_protocol_poll.cc @@ -25,57 +25,3 @@ using namespace std; using namespace Mackie; using namespace PBD; -const char * MackieControlProtocol::default_port_name = "mcu"; - -bool MackieControlProtocol::probe() -{ - return true; -} - -void MackieControlProtocol::handle_port_inactive( SurfacePort * port ) -{ - // port gone away. So stop polling it ASAP - { - // delete the port instance - Glib::Mutex::Lock lock( update_mutex ); - MackiePorts::iterator it = find( _ports.begin(), _ports.end(), port ); - if ( it != _ports.end() ) - { - delete *it; - _ports.erase( it ); - } - } - - // TODO all the rebuilding of surfaces and so on -} - -void MackieControlProtocol::handle_port_active (SurfacePort *) -{ - // no need to re-add port because it was already added - // during the init phase. So just update the local surface - // representation and send the representation to - // all existing ports - - // TODO update bank size - - // TODO rebuild surface, to have new units - - // finally update session state to the surface - // TODO but this is also done in set_active, and - // in fact update_surface won't execute unless -#ifdef DEBUG - cout << "update_surface in handle_port_active" << endl; -#endif - // _active == true - update_surface(); -} - -void MackieControlProtocol::handle_port_init (Mackie::SurfacePort *) -{ -#ifdef DEBUG - cout << "MackieControlProtocol::handle_port_init" << endl; -#endif -#ifdef DEBUG - cout << "MackieControlProtocol::handle_port_init finish" << endl; -#endif -} diff --git a/libs/surfaces/mackie/mackie_jog_wheel.cc b/libs/surfaces/mackie/mackie_jog_wheel.cc index 95ab97c5f7..8439c68fe7 100644 --- a/libs/surfaces/mackie/mackie_jog_wheel.cc +++ b/libs/surfaces/mackie/mackie_jog_wheel.cc @@ -14,17 +14,17 @@ using namespace Mackie; using std::isnan; -JogWheel::JogWheel( MackieControlProtocol & mcp ) -: _mcp( mcp ) -, _transport_speed( 4.0 ) -, _transport_direction( 0 ) -, _shuttle_speed( 0.0 ) +JogWheel::JogWheel (MackieControlProtocol & mcp) +: _mcp (mcp) +, _transport_speed (4.0) +, _transport_direction (0) +, _shuttle_speed (0.0) { } JogWheel::State JogWheel::jog_wheel_state() const { - if ( !_jog_wheel_states.empty() ) + if (!_jog_wheel_states.empty()) return _jog_wheel_states.top(); else return scroll; @@ -49,28 +49,28 @@ void JogWheel::scroll_event (SurfacePort &, Control &, const ControlState &) void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state) { // TODO use current snap-to setting? - switch ( jog_wheel_state() ) + switch (jog_wheel_state()) { case scroll: - _mcp.ScrollTimeline( state.delta * state.sign ); + _mcp.ScrollTimeline (state.delta * state.sign); break; case zoom: // Chunky Zoom. // TODO implement something similar to ScrollTimeline which // ends up in Editor::control_scroll for smoother zooming. - if ( state.sign > 0 ) - for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomIn(); + if (state.sign > 0) + for (unsigned int i = 0; i < state.ticks; ++i) _mcp.ZoomIn(); else - for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomOut(); + for (unsigned int i = 0; i < state.ticks; ++i) _mcp.ZoomOut(); break; case speed: // locally, _transport_speed is an positive value - _transport_speed += _mcp.surface().scaled_delta( state, _mcp.get_session().transport_speed() ); + _transport_speed += _mcp.surfaces.front()->scaled_delta (state, _mcp.get_session().transport_speed()); // make sure no weirdness gets to the session - if ( _transport_speed < 0 || isnan( _transport_speed ) ) + if (_transport_speed < 0 || isnan (_transport_speed)) { _transport_speed = 0.0; } @@ -81,11 +81,11 @@ void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state) case scrub: { - if ( state.sign != 0 ) + if (state.sign != 0) { - add_scrub_interval( _scrub_timer.restart() ); + add_scrub_interval (_scrub_timer.restart()); // x clicks per second => speed == 1.0 - float speed = _mcp.surface().scrub_scaling_factor() / average_scrub_interval() * state.ticks; + float speed = _mcp.surfaces.front()->scrub_scaling_factor() / average_scrub_interval() * state.ticks; _mcp.get_session().request_transport_speed_nonzero (speed * state.sign); } else @@ -98,7 +98,7 @@ void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state) case shuttle: _shuttle_speed = _mcp.get_session().transport_speed(); - _shuttle_speed += _mcp.surface().scaled_delta( state, _mcp.get_session().transport_speed() ); + _shuttle_speed += _mcp.surfaces.front()->scaled_delta (state, _mcp.get_session().transport_speed()); _mcp.get_session().request_transport_speed_nonzero (_shuttle_speed); break; @@ -111,21 +111,21 @@ void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state) void JogWheel::check_scrubbing() { // if the last elapsed is greater than the average + std deviation, then stop - if ( !_scrub_intervals.empty() && _scrub_timer.elapsed() > average_scrub_interval() + std_dev_scrub_interval() ) + if (!_scrub_intervals.empty() && _scrub_timer.elapsed() > average_scrub_interval() + std_dev_scrub_interval()) { - _mcp.get_session().request_transport_speed( 0.0 ); + _mcp.get_session().request_transport_speed (0.0); _scrub_intervals.clear(); } } -void JogWheel::push( State state ) +void JogWheel::push (State state) { - _jog_wheel_states.push( state ); + _jog_wheel_states.push (state); } void JogWheel::pop() { - if ( _jog_wheel_states.size() > 0 ) + if (_jog_wheel_states.size() > 0) { _jog_wheel_states.pop(); } @@ -133,23 +133,23 @@ void JogWheel::pop() void JogWheel::zoom_state_toggle() { - if ( jog_wheel_state() == zoom ) + if (jog_wheel_state() == zoom) pop(); else - push( zoom ); + push (zoom); } JogWheel::State JogWheel::scrub_state_cycle() { State top = jog_wheel_state(); - if ( top == scrub ) + if (top == scrub) { // stop scrubbing and go to shuttle pop(); - push( shuttle ); + push (shuttle); _shuttle_speed = 0.0; } - else if ( top == shuttle ) + else if (top == shuttle) { // default to scroll, or the last selected pop(); @@ -157,25 +157,25 @@ JogWheel::State JogWheel::scrub_state_cycle() else { // start with scrub - push( scrub ); + push (scrub); } return jog_wheel_state(); } -void JogWheel::add_scrub_interval( unsigned long elapsed ) +void JogWheel::add_scrub_interval (unsigned long elapsed) { - if ( _scrub_intervals.size() > 5 ) + if (_scrub_intervals.size() > 5) { _scrub_intervals.pop_front(); } - _scrub_intervals.push_back( elapsed ); + _scrub_intervals.push_back (elapsed); } float JogWheel::average_scrub_interval() { float sum = 0.0; - for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it ) + for (std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it) { sum += *it; } @@ -188,9 +188,9 @@ float JogWheel::std_dev_scrub_interval() // calculate standard deviation float sum = 0.0; - for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it ) + for (std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it) { - sum += pow( *it - average, 2 ); + sum += pow (*it - average, 2); } - return sqrt( sum / _scrub_intervals.size() -1 ); + return sqrt (sum / _scrub_intervals.size() -1); } diff --git a/libs/surfaces/mackie/mackie_midi_builder.cc b/libs/surfaces/mackie/mackie_midi_builder.cc index 714180112d..a57725baa5 100644 --- a/libs/surfaces/mackie/mackie_midi_builder.cc +++ b/libs/surfaces/mackie/mackie_midi_builder.cc @@ -39,6 +39,7 @@ #include "meter.h" #include "midi_byte_array.h" #include "mackie_port.h" +#include "surface.h" using namespace PBD; using namespace Mackie; @@ -77,7 +78,7 @@ MidiByteArray MackieMidiBuilder::build_led_ring (const LedRing & led_ring, const // the control type , midi_pot_id // the id - , 0x20 + led_ring.control_id() + , 0x20 + led_ring.raw_id() // the value , calculate_pot_value (mode, state) ); @@ -101,7 +102,7 @@ MidiByteArray MackieMidiBuilder::build_led (const Led & led, LedState ls) return MidiByteArray (3 , midi_button_id - , led.control_id() + , led.raw_id() , state ); } @@ -111,7 +112,7 @@ MidiByteArray MackieMidiBuilder::build_fader (const Fader & fader, float pos) int posi = int (0x3fff * pos); return MidiByteArray (3 - , midi_fader_id | fader.control_id() + , midi_fader_id | fader.raw_id() // lower-order bits , posi & 0x7f // higher-order bits @@ -119,7 +120,7 @@ MidiByteArray MackieMidiBuilder::build_fader (const Fader & fader, float pos) ); } -MidiByteArray MackieMidiBuilder::zero_strip (SurfacePort & port, const Strip & strip) +MidiByteArray MackieMidiBuilder::zero_strip (Surface& surface, const Strip & strip) { Group::Controls::const_iterator it = strip.controls().begin(); MidiByteArray retval; @@ -134,8 +135,8 @@ MidiByteArray MackieMidiBuilder::zero_strip (SurfacePort & port, const Strip & s /* XXX: not sure about this check to only display stuff for strips of index < 8 */ if (strip.index() < 8) { - retval << strip_display_blank (port, strip, 0); - retval << strip_display_blank (port, strip, 1); + retval << strip_display_blank (surface, strip, 0); + retval << strip_display_blank (surface, strip, 1); } return retval; @@ -202,23 +203,23 @@ MidiByteArray MackieMidiBuilder::two_char_display (unsigned int value, const std return two_char_display (os.str()); } -MidiByteArray MackieMidiBuilder::strip_display_blank (SurfacePort & port, const Strip & strip, unsigned int line_number) +MidiByteArray MackieMidiBuilder::strip_display_blank (Surface& surface, const Strip & strip, unsigned int line_number) { // 6 spaces, not 7 because strip_display adds a space where appropriate - return strip_display (port, strip, line_number, " "); + return strip_display (surface, strip, line_number, " "); } -MidiByteArray MackieMidiBuilder::strip_display (SurfacePort & port, const Strip & strip, unsigned int line_number, const std::string & line) +MidiByteArray MackieMidiBuilder::strip_display (Surface& surface, const Strip & strip, unsigned int line_number, const std::string & line) { assert (line_number <= 1); MidiByteArray retval; - uint32_t index = strip.index() % port.strips(); + uint32_t index = strip.index() % 8; DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display index: %1, line %2 = %3\n", strip.index(), line_number, line)); // sysex header - retval << port.sysex_hdr(); + retval << surface.sysex_hdr(); // code for display retval << 0x12; @@ -254,7 +255,8 @@ MidiByteArray MackieMidiBuilder::all_strips_display (SurfacePort & /*port*/, std return retval; } -MidiByteArray MackieMidiBuilder::timecode_display (SurfacePort & port, const std::string & timecode, const std::string & last_timecode) +MidiByteArray +MackieMidiBuilder::timecode_display (Surface& surface, const std::string & timecode, const std::string & last_timecode) { // if there's no change, send nothing, not even sysex header if (timecode == last_timecode) return MidiByteArray(); @@ -278,7 +280,7 @@ MidiByteArray MackieMidiBuilder::timecode_display (SurfacePort & port, const std MidiByteArray retval; // sysex header - retval << port.sysex_hdr(); + retval << surface.sysex_hdr(); // code for timecode display retval << 0x10; diff --git a/libs/surfaces/mackie/mackie_midi_builder.h b/libs/surfaces/mackie/mackie_midi_builder.h index 1b33c75913..e4f929a588 100644 --- a/libs/surfaces/mackie/mackie_midi_builder.h +++ b/libs/surfaces/mackie/mackie_midi_builder.h @@ -41,69 +41,72 @@ class LedRing; class MackieMidiBuilder { public: + MackieMidiBuilder () {} + ~MackieMidiBuilder() {} + /** The first byte of a midi message from the surface will contain one of these, sometimes bitmasked with the control id */ enum midi_types { - midi_fader_id = Control::type_fader - , midi_button_id = Control::type_button - , midi_pot_id = Control::type_pot + midi_fader_id = Control::type_fader, + midi_button_id = Control::type_button, + midi_pot_id = Control::type_pot, }; /** The LED rings have these modes. */ enum midi_pot_mode { - midi_pot_mode_dot = 0 - , midi_pot_mode_boost_cut = 1 - , midi_pot_mode_wrap = 2 - , midi_pot_mode_spread = 3 + midi_pot_mode_dot = 0, + midi_pot_mode_boost_cut = 1, + midi_pot_mode_wrap = 2, + midi_pot_mode_spread = 3, }; - MidiByteArray build_led_ring( const Pot & pot, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot ); - MidiByteArray build_led_ring( const LedRing & led_ring, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot ); + MidiByteArray build_led_ring (const Pot & pot, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot); + MidiByteArray build_led_ring (const LedRing & led_ring, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot); - MidiByteArray build_led( const Led & led, LedState ls ); - MidiByteArray build_led( const Button & button, LedState ls ); + MidiByteArray build_led (const Led & led, LedState ls); + MidiByteArray build_led (const Button & button, LedState ls); - MidiByteArray build_fader( const Fader & fader, float pos ); + MidiByteArray build_fader (const Fader & fader, float pos); /// return bytes that will reset all controls to their zero positions - /// And blank the display for the strip. Pass SurfacePort so we know which sysex header to use. - MidiByteArray zero_strip( SurfacePort &, const Strip & strip ); + /// And blank the display for the strip. Pass Surface so we know which sysex header to use. + MidiByteArray zero_strip (Surface&, const Strip & strip); // provide bytes to zero the given control - MidiByteArray zero_control( const Control & control ); + MidiByteArray zero_control (const Control & control); // display the first 2 chars of the msg in the 2 char display // . is appended to the previous character, so A.B. would // be two characters - MidiByteArray two_char_display( const std::string & msg, const std::string & dots = " " ); - MidiByteArray two_char_display( unsigned int value, const std::string & dots = " " ); + MidiByteArray two_char_display (const std::string & msg, const std::string & dots = " "); + MidiByteArray two_char_display (unsigned int value, const std::string & dots = " "); /** Timecode display. Only the difference between timecode and last_timecode will be encoded, to save midi bandwidth. If they're the same, an empty array will be returned */ - MidiByteArray timecode_display( SurfacePort &, const std::string & timecode, const std::string & last_timecode = "" ); + MidiByteArray timecode_display (Surface&, const std::string & timecode, const std::string & last_timecode = ""); /** for displaying characters on the strip LCD pass SurfacePort so we know which sysex header to use */ - MidiByteArray strip_display( SurfacePort &, const Strip & strip, unsigned int line_number, const std::string & line ); + MidiByteArray strip_display (Surface &, const Strip & strip, unsigned int line_number, const std::string & line); - /// blank the strip LCD, ie write all spaces. Pass SurfacePort so we know which sysex header to use. - MidiByteArray strip_display_blank( SurfacePort &, const Strip & strip, unsigned int line_number ); + /// blank the strip LCD, ie write all spaces. Pass Surface so we know which sysex header to use. + MidiByteArray strip_display_blank (Surface&, const Strip & strip, unsigned int line_number); /// for generating all strip names. Pass SurfacePort so we know which sysex header to use. - MidiByteArray all_strips_display( SurfacePort &, std::vector<std::string> & lines1, std::vector<std::string> & lines2 ); + MidiByteArray all_strips_display (SurfacePort &, std::vector<std::string> & lines1, std::vector<std::string> & lines2); protected: - static MIDI::byte calculate_pot_value( midi_pot_mode mode, const ControlState & ); + static MIDI::byte calculate_pot_value (midi_pot_mode mode, const ControlState &); }; } diff --git a/libs/surfaces/mackie/mackie_port.cc b/libs/surfaces/mackie/mackie_port.cc deleted file mode 100644 index 8c967b787d..0000000000 --- a/libs/surfaces/mackie/mackie_port.cc +++ /dev/null @@ -1,416 +0,0 @@ -/* - Copyright (C) 2006,2007 John Anderson - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include <sstream> - -#include <glibmm/main.h> -#include <boost/shared_array.hpp> - -#include "mackie_port.h" - -#include "mackie_control_exception.h" -#include "mackie_control_protocol.h" -#include "mackie_midi_builder.h" -#include "controls.h" -#include "surface.h" - -#include "fader.h" -#include "button.h" -#include "strip.h" -#include "pot.h" -#include "control_group.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 & 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) -{ - 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) - { - 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"); - } - } - else - { - // must be an extender, ie no master fader - return 8; - } -} - -// should really be in MackiePort -void MackiePort::open() -{ - 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() -{ - DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::close\n"); - - // disconnect signals - - sysex_connection.disconnect(); - ScopedConnectionList::drop_connections (); - _connected = false; - - // TODO emit a "closing" signal? -} - -const MidiByteArray & MackiePort::sysex_hdr() const -{ - switch (_port_type) - { - case mcu: return mackie_sysex_hdr; - case ext: return mackie_sysex_hdr_xt; - } - cout << "MackiePort::sysex_hdr _port_type not known" << endl; - return mackie_sysex_hdr; -} - -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 MackiePort::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) { - finalise_init (false); - 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 MackiePort::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) { - finalise_init (false); - ostringstream os; - os << "expecting 14 bytes, read " << bytes << " from " << input_port().name(); - throw MackieControlException (os.str()); - } - - // send version request - return MidiByteArray (2, 0x13, 0x00); -} - -void MackiePort::probe_emulation (const MidiByteArray &) -{ -#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; - return; - } - - finalise_init (true); -} - -void MackiePort::init() -{ - DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::init\n"); - - init_mutex.lock(); - _initialising = true; - - 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() - - // 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) -{ - 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; - 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 - 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(); - - /* V-Pot messages are Controller */ - p->controller.connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_controller_message, this, _1, _2)); - /* Button messages are NoteOn */ - p->note_on.connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_note_on_message, this, _1, _2)); - /* Fader messages are Pitchbend */ - 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) { - 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"); - } - DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active returning\n"); - return SurfacePort::active(); -} - -void MackiePort::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: - write_sysex (host_connection_query (bytes)); - break; - case 0x03: - // not used right now - 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; - } -} - -void -MackiePort::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uint32_t fader_id) -{ - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi pitchbend on port %3 (number %4), fader = %1 value = %2\n", - (8*number()) + fader_id, pb, *this, number())); - - Control* control = _mcp.surface().faders[(8*number()) + fader_id]; - - if (control) { - float midi_pos = pb >> 4; // only the top 10 bytes are used - _mcp.handle_control_event (*this, *control, midi_pos / 1023.0); - } else { - DEBUG_TRACE (DEBUG::MackieControl, "fader not found\n"); - } -} - -void -MackiePort::handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes* ev) -{ - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackiePort::handle_note_on %1 = %2\n", ev->note_number, ev->velocity)); - - Control* control = _mcp.surface().buttons[(8*number()) + ev->note_number]; - - if (control) { - ControlState control_state (ev->velocity == 0x7f ? press : release); - control->set_in_use (control_state.button_state == press); - control_event (*this, *control, control_state); - } else { - DEBUG_TRACE (DEBUG::MackieControl, "button not found\n"); - } -} - -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 = _mcp.surface().pots[(8*number()) + ev->controller_number]; - - if (!control && ev->controller_number == Control::jog_base_id) { - control = _mcp.surface().controls_by_name["jog"]; - } - - 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); - } else { - DEBUG_TRACE (DEBUG::MackieControl, "pot not found\n"); - } -} - -void -MackiePort::control_event (SurfacePort& sp, Control& c, const ControlState& cs) -{ - _mcp.handle_control_event (sp, c, cs); -} diff --git a/libs/surfaces/mackie/mackie_port.h b/libs/surfaces/mackie/mackie_port.h deleted file mode 100644 index 1b6b77da93..0000000000 --- a/libs/surfaces/mackie/mackie_port.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - Copyright (C) 2006,2007 John Anderson - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -#ifndef mackie_port_h -#define mackie_port_h - -#include <midi++/types.h> -#include <glibmm/thread.h> - -#include "pbd/signals.h" - -#include "surface_port.h" -#include "midi_byte_array.h" -#include "types.h" - -namespace MIDI { - class Port; - class Parser; -} - -class MackieControlProtocol; - -namespace Mackie -{ - -class MackiePort : public SurfacePort -{ -public: - enum port_type_t { mcu, ext }; - enum emulation_t { none, mackie, bcf2000 }; - - MackiePort (MackieControlProtocol & mcp, MIDI::Port & input_port, MIDI::Port & output_port, int number, port_type_t = mcu); - ~MackiePort(); - - virtual void open(); - virtual void close(); - - /// MCU and extenders have different sysex headers - virtual const MidiByteArray & sysex_hdr() const; - - /// Handle device initialisation - void handle_midi_sysex( MIDI::Parser &, MIDI::byte *, size_t count ); - void handle_midi_pitchbend_message (MIDI::Parser &, MIDI::pitchbend_t, uint32_t channel_id); - void handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes*); - void handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes*); - - /// return the number of strips associated with this port - virtual int strips() const; - - /// Block until the port has finished initialising, and then return - /// whether the intialisation succeeded - bool wait_for_init(); - - emulation_t emulation() const { return _emulation; } - - /// Connect the any signal from the parser to handle_midi_any - /// unless it's already connected - void connect_to_signals (); - -protected: - /** - The initialisation sequence is fairly complex. First a lock is acquired - so that a condition can be used to signal the end of the init process. - Then a sysex is sent to the device. The response to the sysex - is handled by a switch in handle_midi_sysex which calls one of the - other methods. - - However, windows DAWs ignore the documented init sequence and so we - do too. Thanks to Essox for helping with this. - - So we use the version firmware to figure out what device is on - the other end of the cable. - */ - void init(); - - /** - Once the device is initialised, finalise_init(true) is called, which - releases the lock and signals the condition, and starts handling incoming - messages. finalise_init(false) will also release the lock but doesn't - start handling messages. - */ - void finalise_init( bool yn ); - - MidiByteArray host_connection_query( MidiByteArray & bytes ); - MidiByteArray host_connection_confirmation( const MidiByteArray & bytes ); - - /** - Will set _emulation to what it thinks is correct, based - on responses from the device. Or get/set parameters. Or - environment variables. Or existence of a file. - */ - void probe_emulation( const MidiByteArray & bytes ); - - void control_event (SurfacePort &, Control &, const ControlState &); - -private: - MackieControlProtocol & _mcp; - port_type_t _port_type; - PBD::ScopedConnection any_connection; - PBD::ScopedConnection sysex_connection; - emulation_t _emulation; - - bool _initialising; - bool _connected; - Glib::Cond init_cond; - Glib::Mutex init_mutex; -}; - -} - -#endif diff --git a/libs/surfaces/mackie/mackie_surface.cc b/libs/surfaces/mackie/mackie_surface.cc deleted file mode 100644 index 415d4b1c4e..0000000000 --- a/libs/surfaces/mackie/mackie_surface.cc +++ /dev/null @@ -1,24 +0,0 @@ -#include <cmath> -#include <sstream> -#include <string> -#include <cstdio> - -#include "controls.h" -#include "mackie_surface.h" -#include "mackie_midi_builder.h" -#include "surface_port.h" - -using namespace Mackie; - -void -MackieSurface::display_timecode (SurfacePort & port, MackieMidiBuilder & builder, const std::string & timecode, const std::string & timecode_last) -{ - port.write (builder.timecode_display (port, timecode, timecode_last)); -} - -float -MackieSurface::scaled_delta (const ControlState & state, float current_speed) -{ - return state.sign * (std::pow (float(state.ticks + 1), 2) + current_speed) / 100.0; -} - diff --git a/libs/surfaces/mackie/mackie_surface.h b/libs/surfaces/mackie/mackie_surface.h deleted file mode 100644 index 18fcab5620..0000000000 --- a/libs/surfaces/mackie/mackie_surface.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef mackie_surface_mackie_h -#define mackie_surface_mackie_h -/* - Generated by scripts/generate-surface.rb -*/ - -#include "surface.h" - -namespace Mackie -{ - -class MackieButtonHandler; -class MackieSurface : public Surface -{ - public: - MackieSurface (uint32_t max_strips) : Surface (max_strips, 8) {} - - virtual bool has_timecode_display() const { return true; } - virtual void display_timecode (SurfacePort &, MackieMidiBuilder &, const std::string & timecode, const std::string & timecode_last); - - virtual float scrub_scaling_factor() { return 100.0; } - virtual float scaled_delta (const ControlState & state, float current_speed); -}; - -} - -#endif diff --git a/libs/surfaces/mackie/mcp_buttons.cc b/libs/surfaces/mackie/mcp_buttons.cc index 84674cb7af..253b39db82 100644 --- a/libs/surfaces/mackie/mcp_buttons.cc +++ b/libs/surfaces/mackie/mcp_buttons.cc @@ -25,6 +25,7 @@ #include "ardour/rc_configuration.h" #include "mackie_control_protocol.h" +#include "surface.h" #include "i18n.h" @@ -89,8 +90,8 @@ LedState MackieControlProtocol::left_press (Button &) { Sorted sorted = get_sorted_routes(); - if (sorted.size() > route_table.size()) { - int new_initial = _current_initial_bank - route_table.size(); + if (sorted.size() > n_strips()) { + int new_initial = _current_initial_bank - n_strips(); if (new_initial < 0) { new_initial = 0; } @@ -116,11 +117,13 @@ LedState MackieControlProtocol::right_press (Button &) { Sorted sorted = get_sorted_routes(); - if (sorted.size() > route_table.size()) { - uint32_t delta = sorted.size() - (route_table.size() + _current_initial_bank); + uint32_t strip_cnt = n_strips(); - if (delta > route_table.size()) { - delta = route_table.size(); + if (sorted.size() > strip_cnt) { + uint32_t delta = sorted.size() - (strip_cnt + _current_initial_bank); + + if (delta > strip_cnt) { + delta = strip_cnt; } if (delta > 0) { @@ -230,7 +233,7 @@ LedState MackieControlProtocol::channel_left_press (Button &) { Sorted sorted = get_sorted_routes(); - if (sorted.size() > route_table.size()) { + if (sorted.size() > n_strips()) { prev_track(); return on; } else { @@ -248,7 +251,7 @@ LedState MackieControlProtocol::channel_right_press (Button &) { Sorted sorted = get_sorted_routes(); - if (sorted.size() > route_table.size()) { + if (sorted.size() > n_strips()) { next_track(); return on; } else { @@ -505,17 +508,21 @@ MackieControlProtocol::record_release (Button &) LedState MackieControlProtocol::rewind_press (Button &) { - _jog_wheel.push (JogWheel::speed); - _jog_wheel.transport_direction (-1); - session->request_transport_speed (-_jog_wheel.transport_speed()); + JogWheel* jog = surfaces.front()->jog_wheel(); + assert (jog); + jog->push (JogWheel::speed); + jog->transport_direction (-1); + session->request_transport_speed (-jog->transport_speed()); return on; } LedState MackieControlProtocol::rewind_release (Button &) { - _jog_wheel.pop(); - _jog_wheel.transport_direction (0); + JogWheel* jog = surfaces.front()->jog_wheel(); + assert (jog); + jog->pop(); + jog->transport_direction (0); if (_transport_previously_rolling) { session->request_transport_speed (1.0); } else { @@ -527,17 +534,21 @@ MackieControlProtocol::rewind_release (Button &) LedState MackieControlProtocol::ffwd_press (Button &) { - _jog_wheel.push (JogWheel::speed); - _jog_wheel.transport_direction (1); - session->request_transport_speed (_jog_wheel.transport_speed()); + JogWheel* jog = surfaces.front()->jog_wheel(); + assert (jog); + jog->push (JogWheel::speed); + jog->transport_direction (1); + session->request_transport_speed (jog->transport_speed()); return on; } LedState MackieControlProtocol::ffwd_release (Button &) { - _jog_wheel.pop(); - _jog_wheel.transport_direction (0); + JogWheel* jog = surfaces.front()->jog_wheel(); + assert (jog); + jog->pop(); + jog->transport_direction (0); if (_transport_previously_rolling) { session->request_transport_speed (1.0); } else { diff --git a/libs/surfaces/mackie/meter.cc b/libs/surfaces/mackie/meter.cc index a7dbf25831..5bc7c49096 100644 --- a/libs/surfaces/mackie/meter.cc +++ b/libs/surfaces/mackie/meter.cc @@ -31,9 +31,9 @@ using namespace Mackie; using namespace PBD; Control* -Meter::factory (Surface& surface, int id, int ordinal, const char* name, Group& group) +Meter::factory (Surface& surface, int id, const char* name, Group& group) { - Meter* m = new Meter (id, ordinal, name, group); + Meter* m = new Meter (id, name, group); surface.meters[id] = m; surface.controls.push_back (m); group.add (*m); diff --git a/libs/surfaces/mackie/meter.h b/libs/surfaces/mackie/meter.h index 70d44e4515..7110f04416 100644 --- a/libs/surfaces/mackie/meter.h +++ b/libs/surfaces/mackie/meter.h @@ -30,8 +30,8 @@ class SurfacePort; class Meter : public Control { public: - Meter (int id, int ordinal, std::string name, Group & group) - : Control (id, ordinal, name, group) + Meter (int id, std::string name, Group & group) + : Control (id, name, group) , last_segment_value_sent (-1) , overload_on (false) {} @@ -39,7 +39,7 @@ public: MidiByteArray update_message (float dB); - static Control* factory (Surface&, int id, int ordinal, const char*, Group&); + static Control* factory (Surface&, int id, const char*, Group&); int last_segment_value_sent; diff --git a/libs/surfaces/mackie/pot.h b/libs/surfaces/mackie/pot.h index 42e5c643e8..767ea6e6df 100644 --- a/libs/surfaces/mackie/pot.h +++ b/libs/surfaces/mackie/pot.h @@ -9,15 +9,15 @@ namespace Mackie { class Pot : public Control { public: - Pot (int id, int ordinal, std::string name, Group & group) - : Control (id, ordinal, name, group) - , _led_ring (id, ordinal, name + "_ring", group) {} + Pot (int id, std::string name, Group & group) + : Control (id, name, group) + , _led_ring (id, name + "_ring", group) {} virtual type_t type() const { return type_pot; } virtual const LedRing & led_ring() const {return _led_ring; } - static Control* factory (Surface&, int id, int ordinal, const char*, Group&); + static Control* factory (Surface&, int id, const char*, Group&); private: LedRing _led_ring; diff --git a/libs/surfaces/mackie/route_signal.cc b/libs/surfaces/mackie/route_signal.cc deleted file mode 100644 index f40fff48ba..0000000000 --- a/libs/surfaces/mackie/route_signal.cc +++ /dev/null @@ -1,105 +0,0 @@ -/* - Copyright (C) 2006,2007 John Anderson - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include <stdexcept> - -#include "ardour/route.h" -#include "ardour/track.h" -#include "ardour/midi_ui.h" -#include "ardour/pannable.h" -#include "ardour/session_object.h" // for Properties::name - -#include "mackie_control_protocol.h" -#include "route_signal.h" -#include "strip.h" - -using namespace ARDOUR; -using namespace Mackie; -using namespace std; - -#define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */ -#define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__)) - -void RouteSignal::connect() -{ - if (_strip.has_solo()) { - _route->solo_control()->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_solo_changed, &_mcp, this), midi_ui_context()); - } - - if (_strip.has_mute()) { - _route->mute_control()->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_mute_changed, &_mcp, this), midi_ui_context()); - } - - if (_strip.has_gain()) { - _route->gain_control()->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_gain_changed, &_mcp, this, false), midi_ui_context()); - } - - _route->PropertyChanged.connect (connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_property_changed, &_mcp, _1, this), midi_ui_context()); - - if (_route->pannable()) { - _route->pannable()->pan_azimuth_control->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_panner_changed, &_mcp, this, false), midi_ui_context()); - _route->pannable()->pan_width_control->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_panner_changed, &_mcp, this, false), midi_ui_context()); - } - - boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<ARDOUR::Track>(_route); - if (trk) { - trk->rec_enable_control()->Changed .connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_record_enable_changed, &_mcp, this), midi_ui_context()); - } - - // TODO this works when a currently-banked route is made inactive, but not - // when a route is activated which should be currently banked. - _route->active_changed.connect (connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_active_changed, &_mcp, this), midi_ui_context()); - - _route->DropReferences.connect (connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::refresh_current_bank, &_mcp), midi_ui_context()); - - // TODO - // SelectedChanged - // RemoteControlIDChanged. Better handled at Session level. -} - -void -RouteSignal::disconnect() -{ - connections.drop_connections (); -} - -void -RouteSignal::notify_all() -{ - if (_strip.has_solo()) { - _mcp.notify_solo_changed (this); - } - - if (_strip.has_mute()) { - _mcp.notify_mute_changed (this); - } - - if (_strip.has_gain()) { - _mcp.notify_gain_changed (this); - } - - _mcp.notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name), this); - - if (_strip.has_vpot()) { - _mcp.notify_panner_changed (this); - } - - if (_strip.has_recenable()) { - _mcp.notify_record_enable_changed (this); - } -} diff --git a/libs/surfaces/mackie/route_signal.h b/libs/surfaces/mackie/route_signal.h deleted file mode 100644 index 59bfc66e7b..0000000000 --- a/libs/surfaces/mackie/route_signal.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright (C) 2006,2007 John Anderson - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -#ifndef route_signal_h -#define route_signal_h - -#include <vector> -#include <boost/shared_ptr.hpp> - -#include "pbd/signals.h" - -#include "midi_byte_array.h" - -class MackieControlProtocol; - -namespace ARDOUR { - class Route; -} - -namespace Mackie -{ - -class Strip; -class SurfacePort; - -/** - This class is intended to easily create and destroy the set of - connections from a route to a control surface strip. Instantiating - it will connect the signals, and destructing it will disconnect - the signals. -*/ -class RouteSignal -{ -public: - RouteSignal(boost::shared_ptr<ARDOUR::Route> route, MackieControlProtocol & mcp, Strip & strip, SurfacePort & port ) - : _route( route ), _mcp( mcp ), _strip( strip ), _port( port ), _last_gain_written(0.0) - { - connect(); - } - - ~RouteSignal() - { - disconnect(); - } - - void connect(); - void disconnect(); - - // call all signal handlers manually - void notify_all(); - - boost::shared_ptr<const ARDOUR::Route> route() const { return _route; } - Strip & strip() { return _strip; } - SurfacePort & port() { return _port; } - - float last_gain_written() const { return _last_gain_written; } - void last_gain_written( float other ) { _last_gain_written = other; } - - const MidiByteArray & last_pan_written() const { return _last_pan_written; } - void last_pan_written( const MidiByteArray & other ) { _last_pan_written = other; } - -private: - boost::shared_ptr<ARDOUR::Route> _route; - MackieControlProtocol & _mcp; - Strip & _strip; - SurfacePort & _port; - - PBD::ScopedConnectionList connections; - - // Last written values for the gain and pan, to avoid overloading - // the midi connection to the surface - float _last_gain_written; - MidiByteArray _last_pan_written; -}; - -} - -#endif diff --git a/libs/surfaces/mackie/strip.cc b/libs/surfaces/mackie/strip.cc index e20f33e1df..fd9f4299d0 100644 --- a/libs/surfaces/mackie/strip.cc +++ b/libs/surfaces/mackie/strip.cc @@ -21,6 +21,22 @@ #include <stdint.h> #include "strip.h" +#include "midi++/port.h" + +#include "pbd/compose.h" +#include "pbd/convert.h" + +#include "ardour/debug.h" +#include "ardour/midi_ui.h" +#include "ardour/route.h" +#include "ardour/track.h" +#include "ardour/pannable.h" +#include "ardour/panner.h" +#include "ardour/rc_configuration.h" +#include "ardour/meter.h" + +#include "mackie_control_protocol.h" +#include "surface.h" #include "button.h" #include "led.h" #include "ledring.h" @@ -31,23 +47,16 @@ using namespace Mackie; using namespace std; +using namespace ARDOUR; +using namespace PBD; -Strip::Strip (const std::string& name, int index) - : Group (name) - , _solo (0) - , _recenable (0) - , _mute (0) - , _select (0) - , _vselect (0) - , _fader_touch (0) - , _vpot (0) - , _gain (0) - , _index (index) -{ - /* master strip only */ -} +#define midi_ui_context() ARDOUR::MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */ +#define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__)) + +extern PBD::EventLoop::InvalidationRecord* __invalidator (sigc::trackable& trackable, const char*, int); +#define invalidator(x) __invalidator (*(MidiControlUI::instance()), __FILE__, __LINE__) -Strip::Strip (Surface& surface, const std::string& name, int surface_number, int index, int unit_index, StripControlDefinition* ctls) +Strip::Strip (Surface& s, const std::string& name, int index, StripControlDefinition* ctls) : Group (name) , _solo (0) , _recenable (0) @@ -58,16 +67,22 @@ Strip::Strip (Surface& surface, const std::string& name, int surface_number, int , _vpot (0) , _gain (0) , _index (index) + , _surface (&s) { /* build the controls for this track, which will automatically add them to the Group */ for (uint32_t i = 0; ctls[i].name[0]; ++i) { - ctls[i].factory (surface, ctls[i].base_id + (8*surface_number) + unit_index, unit_index+1, ctls[i].name, *this); + ctls[i].factory (*_surface, ctls[i].base_id + index, ctls[i].name, *this); } } +Strip::~Strip () +{ + +} + /** TODO could optimise this to use enum, but it's only called during the protocol class instantiation. @@ -206,3 +221,268 @@ std::ostream & Mackie::operator << (std::ostream & os, const Strip & strip) return os; } + +void +Strip::set_route (boost::shared_ptr<Route> r) +{ + route_connections.drop_connections (); + + _route = r; + + if (r) { + + if (has_solo()) { + _route->solo_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_solo_changed, this), midi_ui_context()); + } + if (has_mute()) { + _route->mute_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_mute_changed, this), midi_ui_context()); + } + + if (has_gain()) { + _route->gain_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_gain_changed, this, false), midi_ui_context()); + } + + _route->PropertyChanged.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_property_changed, this, _1), midi_ui_context()); + + if (_route->pannable()) { + _route->pannable()->pan_azimuth_control->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_panner_changed, this, false), midi_ui_context()); + _route->pannable()->pan_width_control->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_panner_changed, this, false), midi_ui_context()); + } + + boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<ARDOUR::Track>(_route); + + if (trk) { + trk->rec_enable_control()->Changed .connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_record_enable_changed, this), midi_ui_context()); + } + + // TODO this works when a currently-banked route is made inactive, but not + // when a route is activated which should be currently banked. + + _route->active_changed.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_active_changed, this), midi_ui_context()); + _route->DropReferences.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_route_deleted, this), midi_ui_context()); + + // TODO + // SelectedChanged + // RemoteControlIDChanged. Better handled at Session level. + + /* Update */ + + notify_all (); + } +} + +void +Strip::notify_all() +{ + if (has_solo()) { + notify_solo_changed (); + } + + if (has_mute()) { + notify_mute_changed (); + } + + if (has_gain()) { + notify_gain_changed (); + } + + notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name)); + + if (has_vpot()) { + notify_panner_changed (); + } + + if (has_recenable()) { + notify_record_enable_changed (); + } +} + +void +Strip::notify_solo_changed () +{ + if (_route) { + Button& button = solo(); + _surface->write (builder.build_led (button, _route->soloed())); + } +} + +void +Strip::notify_mute_changed () +{ + if (_route) { + Button & button = mute(); + _surface->write (builder.build_led (button, _route->muted())); + } +} + +void +Strip::notify_record_enable_changed () +{ + if (_route) { + Button & button = recenable(); + _surface->write (builder.build_led (button, _route->record_enabled())); + } +} + +void +Strip::notify_active_changed () +{ + _surface->mcp().refresh_current_bank(); +} + +void +Strip::notify_route_deleted () +{ + _surface->mcp().refresh_current_bank(); +} + +void +Strip::notify_gain_changed (bool force_update) +{ + if (_route) { + Fader & fader = gain(); + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("route %1 gain change, update fader %2 on port %3\n", + _route->name(), + fader.raw_id(), + _surface->port().output_port().name())); + if (!fader.in_use()) { + float gain_value = gain_to_slider_position (_route->gain_control()->get_value()); + // check that something has actually changed + if (force_update || gain_value != _last_gain_written) { + _surface->write (builder.build_fader (fader, gain_value)); + _last_gain_written = gain_value; + } + } + } +} + +void +Strip::notify_property_changed (const PropertyChange& what_changed) +{ + if (!what_changed.contains (ARDOUR::Properties::name)) { + return; + } + + if (_route) { + string line1; + string fullname = _route->name(); + + if (fullname.length() <= 6) { + line1 = fullname; + } else { + line1 = PBD::short_version (fullname, 6); + } + + _surface->write (builder.strip_display (*_surface, *this, 0, line1)); + _surface->write (builder.strip_display_blank (*_surface, *this, 1)); + } +} + +void +Strip::notify_panner_changed (bool force_update) +{ + if (_route) { + Pot & pot = vpot(); + boost::shared_ptr<Panner> panner = _route->panner(); + if (panner) { + double pos = panner->position (); + + // cache the MidiByteArray here, because the mackie led control is much lower + // resolution than the panner control. So we save lots of byte + // sends in spite of more work on the comparison + MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, pos), MackieMidiBuilder::midi_pot_mode_dot); + // check that something has actually changed + if (force_update || bytes != _last_pan_written) + { + _surface->write (bytes); + _last_pan_written = bytes; + } + } else { + _surface->write (builder.zero_control (pot)); + } + } +} + +bool +Strip::handle_button (SurfacePort & port, Control & control, ButtonState bs) +{ + if (!_route) { + // no route so always switch the light off + // because no signals will be emitted by a non-route + _surface->write (builder.build_led (control.led(), off)); + return false; + } + + bool state = false; + + if (bs == press) { + if (control.name() == "recenable") { + state = !_route->record_enabled(); + _route->set_record_enabled (state, this); + } else if (control.name() == "mute") { + state = !_route->muted(); + _route->set_mute (state, this); + } else if (control.name() == "solo") { + state = !_route->soloed(); + _route->set_solo (state, this); + } else if (control.name() == "select") { + _surface->mcp().select_track (_route); + } else if (control.name() == "vselect") { + // TODO could be used to select different things to apply the pot to? + //state = default_button_press (dynamic_cast<Button&> (control)); + } + } + + if (control.name() == "fader_touch") { + + state = (bs == press); + + gain().set_in_use (state); + + if (ARDOUR::Config->get_mackie_emulation() == "bcf" && state) { + + /* BCF faders don't support touch, so add a timeout to reset + their `in_use' state. + */ + + _surface->mcp().add_in_use_timeout (*_surface, gain(), &fader_touch()); + } + } + + return state; +} + +void +Strip::periodic () +{ + if (!_route) { + return; + } + + update_automation (); + update_meter (); +} + +void +Strip::update_automation () +{ + ARDOUR::AutoState gain_state = _route->gain_control()->automation_state(); + + if (gain_state == Touch || gain_state == Play) { + notify_gain_changed (false); + } + + if (_route->panner()) { + ARDOUR::AutoState panner_state = _route->panner()->automation_state(); + if (panner_state == Touch || panner_state == Play) { + notify_panner_changed (false); + } + } +} + +void +Strip::update_meter () +{ + float dB = const_cast<PeakMeter&> (_route->peak_meter()).peak_power (0); + _surface->write (meter().update_message (dB)); +} diff --git a/libs/surfaces/mackie/strip.h b/libs/surfaces/mackie/strip.h index 74928181c7..97997e7f0b 100644 --- a/libs/surfaces/mackie/strip.h +++ b/libs/surfaces/mackie/strip.h @@ -4,7 +4,14 @@ #include <string> #include <iostream> +#include "pbd/property_basics.h" + #include "control_group.h" +#include "mackie_midi_builder.h" + +namespace ARDOUR { + class Route; +} namespace Mackie { @@ -18,13 +25,13 @@ class Meter; struct StripControlDefinition { const char* name; uint32_t base_id; - Control* (*factory)(Surface&, int index, int ordinal, const char* name, Group&); + Control* (*factory)(Surface&, int index, const char* name, Group&); }; struct GlobalControlDefinition { const char* name; uint32_t id; - Control* (*factory)(Surface&, int index, int ordinal, const char* name, Group&); + Control* (*factory)(Surface&, int index, const char* name, Group&); const char* group_name; }; @@ -34,11 +41,12 @@ struct GlobalControlDefinition { class Strip : public Group { public: - Strip (const std::string& name, int index); /* master strip only */ - Strip (Surface&, const std::string & name, int surface_number, int index, int unit_index, StripControlDefinition* ctls); + Strip (Surface&, const std::string & name, int index, StripControlDefinition* ctls); + ~Strip(); - virtual bool is_strip() const { return true; } - virtual void add (Control & control); + boost::shared_ptr<ARDOUR::Route> route() const { return _route; } + + void add (Control & control); int index() const { return _index; } // zero based Button & solo(); @@ -60,6 +68,16 @@ public: bool has_vpot() const { return _vpot != 0; } bool has_gain() const { return _gain != 0; } bool has_meter() const { return _meter != 0; } + + void set_route (boost::shared_ptr<ARDOUR::Route>); + + // call all signal handlers manually + void notify_all(); + + bool handle_button (SurfacePort & port, Control & control, ButtonState bs); + + void periodic (); + private: Button* _solo; Button* _recenable; @@ -71,19 +89,35 @@ private: Fader* _gain; Meter* _meter; int _index; -}; + Surface* _surface; -std::ostream & operator << (std::ostream &, const Strip &); + MackieMidiBuilder builder; -class MasterStrip : public Strip -{ -public: - MasterStrip (const std::string & name, int index) - : Strip (name, index) {} + boost::shared_ptr<ARDOUR::Route> _route; + PBD::ScopedConnectionList route_connections; + + // Last written values for the gain and pan, to avoid overloading + // the midi connection to the surface + float _last_gain_written; + MidiByteArray _last_pan_written; + + + void notify_solo_changed (); + void notify_mute_changed (); + void notify_record_enable_changed (); + void notify_gain_changed (bool force_update = true); + void notify_property_changed (const PBD::PropertyChange&); + void notify_panner_changed (bool force_update = true); + void notify_active_changed (); + void notify_route_deleted (); - virtual bool is_master() const { return true; } + void update_automation (); + void update_meter (); + }; +std::ostream & operator << (std::ostream &, const Strip &); + } #endif /* __ardour_mackie_control_protocol_strip_h__ */ diff --git a/libs/surfaces/mackie/surface.cc b/libs/surfaces/mackie/surface.cc index 9ec426179d..dda453087d 100644 --- a/libs/surfaces/mackie/surface.cc +++ b/libs/surfaces/mackie/surface.cc @@ -2,13 +2,24 @@ #include <iomanip> #include <iostream> #include <cstdio> +#include <cmath> + +#include "midi++/port.h" +#include "midi++/manager.h" #include "ardour/debug.h" +#include "ardour/route.h" +#include "ardour/panner.h" +#include "ardour/panner_shell.h" +#include "ardour/rc_configuration.h" #include "control_group.h" #include "surface_port.h" #include "surface.h" #include "strip.h" +#include "mackie_midi_builder.h" +#include "mackie_control_protocol.h" +#include "mackie_jog_wheel.h" #include "strip.h" #include "button.h" @@ -19,29 +30,73 @@ #include "jog.h" #include "meter.h" +#include "i18n.h" + using namespace std; using namespace PBD; using namespace Mackie; +using ARDOUR::Route; +using ARDOUR::Panner; +using ARDOUR::Pannable; +using ARDOUR::PannerShell; -Surface::Surface (uint32_t max_strips, uint32_t unit_strips) - : _max_strips (max_strips) - , _unit_strips( unit_strips ) -{ -} +// The MCU sysex header +static MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10); + +// The MCU extender sysex header +static MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11); -void Surface::init () +static MidiByteArray empty_midi_byte_array; + +Surface::Surface (MackieControlProtocol& mcp, jack_client_t* jack, const std::string& device_name, uint32_t number, surface_type_t stype) + : _mcp (mcp) + , _stype (stype) + , _number (number) + , _active (false) + , _connected (false) + , _jog_wheel (0) { DEBUG_TRACE (DEBUG::MackieControl, "Surface::init\n"); + + MIDI::Manager * mm = MIDI::Manager::instance(); + MIDI::Port * input = mm->add_port (new MIDI::Port (string_compose (_("%1 in"), device_name), MIDI::Port::IsInput, jack)); + MIDI::Port * output = mm->add_port (new MIDI::Port (string_compose (_("%1 out"), device_name), MIDI::Port::IsOutput, jack)); + + 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)); - strips.resize (_max_strips); - init_controls (); - init_strips (); + switch (stype) { + case mcu: + init_controls (); + _jog_wheel = new Mackie::JogWheel (_mcp); + break; + default: + break; + } + + switch (stype) { + case mcu: + case ext: + strips.resize (8); + init_strips (); + break; + default: + break; + } DEBUG_TRACE (DEBUG::MackieControl, "Surface::init finish\n"); } Surface::~Surface () { + DEBUG_TRACE (DEBUG::MackieControl, "Surface: destructor\n"); + + zero_all (); + // delete groups for (Groups::iterator it = groups.begin(); it != groups.end(); ++it) { delete it->second; @@ -51,6 +106,20 @@ Surface::~Surface () for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) { delete *it; } + + delete _jog_wheel; + delete _port; +} + +const MidiByteArray& +Surface::sysex_hdr() const +{ + switch (_stype) { + case mcu: return mackie_sysex_hdr; + case ext: return mackie_sysex_hdr_xt; + } + cout << "SurfacePort::sysex_hdr _port_type not known" << endl; + return mackie_sysex_hdr; } static GlobalControlDefinition mackie_global_controls[] = { @@ -143,14 +212,11 @@ Surface::init_controls() groups["none"] = new Group ("none"); groups["transport"] = new Group ("transport"); groups["user"] = new Group ("user"); - - group = new MasterStrip ("master", 0); - groups["master"] = group; - strips[0] = dynamic_cast<Strip*> (group); + groups["master"] = new Group ("master"); for (uint32_t n = 0; mackie_global_controls[n].name[0]; ++n) { group = groups[mackie_global_controls[n].group_name]; - Control* control = mackie_global_controls[n].factory (*this, mackie_global_controls[n].id, 1, mackie_global_controls[n].name, *group); + Control* control = mackie_global_controls[n].factory (*this, mackie_global_controls[n].id, mackie_global_controls[n].name, *group); controls_by_name[mackie_global_controls[n].name] = control; group->add (*control); } @@ -172,20 +238,366 @@ static StripControlDefinition mackie_strip_controls[] = { void Surface::init_strips () { - for (uint32_t i = 0; i < _max_strips; ++i) { + for (uint32_t i = 0; i < 8; ++i) { char name[32]; - uint32_t unit_index = i % _unit_strips; - - snprintf (name, sizeof (name), "strip_%d", unit_index+1); + snprintf (name, sizeof (name), "strip_%d", (8* _number) + i); - cerr << "Register strip " << i << " unit index " << unit_index << endl; + cerr << "Register strip " << i << endl; - Strip* strip = new Strip (*this, name, i/8, i, unit_index, mackie_strip_controls); + Strip* strip = new Strip (*this, name, i, mackie_strip_controls); groups[name] = strip; strips[i] = strip; } } +void +Surface::display_timecode (const std::string & timecode, const std::string & timecode_last) +{ + if (has_timecode_display()) { + _port->write (builder.timecode_display (*this, timecode, timecode_last)); + } +} + +float +Surface::scaled_delta (const ControlState & state, float current_speed) +{ + return state.sign * (std::pow (float(state.ticks + 1), 2) + current_speed) / 100.0; +} + +void +Surface::display_bank_start (uint32_t current_bank) +{ + if (current_bank == 0) { + // send Ar. to 2-char display on the master + _port->write (builder.two_char_display ("Ar", "..")); + } else { + // write the current first remote_id to the 2-char display + _port->write (builder.two_char_display (current_bank)); + } +} + +void +Surface::blank_jog_ring () +{ + Control* control = controls_by_name["jog"]; + + if (control) { + _port->write (builder.build_led_ring (*(dynamic_cast<Pot*> (control)), off)); + } +} + +bool +Surface::has_timecode_display () const +{ + return false; +} + +float +Surface::scrub_scaling_factor () const +{ + return 100.0; +} + +void +Surface::connect_to_signals () +{ + if (!_connected) { + + MIDI::Parser* p = _port->input_port().parser(); + + /* 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 */ + p->note_on.connect_same_thread (*this, boost::bind (&Surface::handle_midi_note_on_message, this, _1, _2)); + /* Fader messages are Pitchbend */ + p->channel_pitchbend[0].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 0U)); + p->channel_pitchbend[1].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 1U)); + p->channel_pitchbend[2].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 2U)); + p->channel_pitchbend[3].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 3U)); + p->channel_pitchbend[4].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 4U)); + p->channel_pitchbend[5].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 5U)); + p->channel_pitchbend[6].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 6U)); + p->channel_pitchbend[7].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 7U)); + + _connected = true; + } +} + + +void +Surface::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uint32_t fader_id) +{ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi pitchbend on port %3, fader = %1 value = %2\n", + fader_id, pb, _number)); + + Control* control = faders[fader_id]; + + if (control) { + float midi_pos = pb >> 4; // only the top 10 bytes are used + handle_control_event (*control, midi_pos / 1023.0); + } else { + DEBUG_TRACE (DEBUG::MackieControl, "fader not found\n"); + } +} + +void +Surface::handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes* ev) +{ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_note_on %1 = %2\n", ev->note_number, ev->velocity)); + + Control* control = buttons[ev->note_number]; + + if (control) { + ControlState control_state (ev->velocity == 0x7f ? press : release); + control->set_in_use (control_state.button_state == press); + handle_control_event (*control, control_state); + } else { + DEBUG_TRACE (DEBUG::MackieControl, "button not found\n"); + } +} + +void +Surface::handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes* ev) +{ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_midi_controller %1 = %2\n", ev->controller_number, ev->value)); + + Control* control = pots[ev->controller_number]; + + if (!control && ev->controller_number == Control::jog_base_id) { + control = controls_by_name["jog"]; + } + + 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); + + handle_control_event (*control, state); + } else { + DEBUG_TRACE (DEBUG::MackieControl, "pot not found\n"); + } +} + +void +Surface::handle_control_event (Control & control, const ControlState & state) +{ + // find the route for the control, if there is one + boost::shared_ptr<Route> route; + Strip* strip; + + if ((strip = dynamic_cast<Strip*> (&control.group())) != 0) { + route = strip->route (); + } + + // This handles control element events from the surface + // the state of the controls on the surface is usually updated + // from UI events. + + switch (control.type()) { + case Control::type_fader: + // find the route in the route table for the id + // if the route isn't available, skip it + // at which point the fader should just reset itself + if (route != 0) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("fader to %1\n", state.pos)); + + route->gain_control()->set_value (slider_position_to_gain (state.pos)); + + if (ARDOUR::Config->get_mackie_emulation() == "bcf") { + /* reset the timeout while we're still moving the fader */ + _mcp.add_in_use_timeout (*this, control, control.in_use_touch_control); + } + + // must echo bytes back to slider now, because + // the notifier only works if the fader is not being + // touched. Which it is if we're getting input. + _port->write (builder.build_fader ((Fader&)control, state.pos)); + } + break; + + case Control::type_button: + if (strip) { + strip->handle_button (*_port, control, state.button_state); + } else { + // handle all non-strip buttons + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("global button %1\n", control.id())); + _mcp.handle_button_event (*this, dynamic_cast<Button&>(control), state.button_state); + + } + break; + + // pot (jog wheel, external control) + case Control::type_pot: + if (strip) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip pot %1\n", control.id())); + if (route) { + boost::shared_ptr<Panner> panner = route->panner_shell()->panner(); + // pan for mono input routes, or stereo linked panners + if (panner) { + double p = panner->position (); + + // calculate new value, and adjust + p += state.delta * state.sign; + p = min (1.0, p); + p = max (0.0, p); + panner->set_position (p); + } + } else { + // it's a pot for an umnapped route, so turn all the lights off + _port->write (builder.build_led_ring (dynamic_cast<Pot &> (control), off)); + } + } else { + if (control.is_jog()) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Jog wheel moved %1\n", state.ticks)); + if (_jog_wheel) { + _jog_wheel->jog_event (*_port, control, state); + } + } else { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("External controller moved %1\n", state.ticks)); + cout << "external controller" << state.ticks * state.sign << endl; + } + } + break; + + default: + break; + } +} + +void +Surface::handle_port_inactive (SurfacePort * port) +{ + _active = false; +} + +void +Surface::write_sysex (const MidiByteArray & mba) +{ + if (mba.empty()) { + return; + } + + MidiByteArray buf; + buf << sysex_hdr() << mba << MIDI::eox; + _port->write (buf); +} + +void +Surface::write_sysex (MIDI::byte msg) +{ + MidiByteArray buf; + buf << sysex_hdr() << msg << MIDI::eox; + _port->write (buf); +} + +void +Surface::drop_routes () +{ + for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { + (*s)->set_route (boost::shared_ptr<Route>()); + } +} + +uint32_t +Surface::n_strips () const +{ + return strips.size(); +} + +Strip* +Surface::nth_strip (uint32_t n) const +{ + if (n > n_strips()) { + return 0; + } + return strips[n]; +} + +void +Surface::zero_all () +{ + // TODO turn off Timecode displays + + // zero all strips + for (Strips::iterator it = strips.begin(); it != strips.end(); ++it) { + _port->write (builder.zero_strip (*this, **it)); + } + + // turn off global buttons and leds + // global buttons are only ever on mcu_port, so we don't have + // to figure out which port. + + for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) { + Control & control = **it; + if (!control.group().is_strip() && control.accepts_feedback()) { + _port->write (builder.zero_control (control)); + } + } + + // any hardware-specific stuff + // clear 2-char display + _port->write (builder.two_char_display ("LC")); + + // and the led ring for the master strip + blank_jog_ring (); +} + +void +Surface::periodic () +{ + for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { + (*s)->periodic (); + } +} + +void +Surface::write (const MidiByteArray& data) +{ + _port->write (data); +} + +void +Surface::jog_wheel_state_display (JogWheel::State state) +{ + switch (state) { + case JogWheel::zoom: + _port->write (builder.two_char_display ("Zm")); + break; + case JogWheel::scroll: + _port->write (builder.two_char_display ("Sc")); + break; + case JogWheel::scrub: + _port->write (builder.two_char_display ("Sb")); + break; + case JogWheel::shuttle: + _port->write (builder.two_char_display ("Sh")); + break; + case JogWheel::speed: + _port->write (builder.two_char_display ("Sp")); + break; + case JogWheel::select: + _port->write (builder.two_char_display ("Se")); + break; + } +} + diff --git a/libs/surfaces/mackie/surface.h b/libs/surfaces/mackie/surface.h index ae8b5ad142..9193260f5f 100644 --- a/libs/surfaces/mackie/surface.h +++ b/libs/surfaces/mackie/surface.h @@ -1,9 +1,21 @@ #ifndef mackie_surface_h #define mackie_surface_h +#include <stdint.h> + +#include "midi++/types.h" + #include "controls.h" #include "types.h" -#include <stdint.h> +#include "mackie_midi_builder.h" +#include "mackie_jog_wheel.h" + +namespace MIDI { + class Parser; +} + +class MidiByteArray; +class MackieControlProtocol; namespace Mackie { @@ -19,53 +31,23 @@ class Pot; class Led; class LedRing; -/** - This represents an entire control surface, made up of Groups, - Strips and Controls. There are several collections for - ease of addressing in different ways, but only one collection - has definitive ownership. - - It handles mapping button ids to press_ and release_ calls. - - There are various emulations of the Mackie around, so specific - emulations will inherit from this to change button mapping, or - have 7 fader channels instead of 8, or whatever. - - Currently there are BcfSurface and MackieSurface. - - TODO maybe make Group inherit from Control, for ease of ownership. -*/ -class Surface +class Surface : public PBD::ScopedConnectionList { public: - /** - A Surface can be made up of multiple units. eg one Mackie MCU plus - one or more Mackie MCU extenders. - - \param max_strips is the number of strips for the entire surface. - \param unit_strips is the number of strips per unit. - */ - - Surface (uint32_t max_strips, uint32_t unit_strips); + Surface (MackieControlProtocol&, jack_client_t* jack, const std::string& device_name, uint32_t number, surface_type_t stype); virtual ~Surface(); - /// Calls the virtual initialisation methods. This *must* be called after - /// construction, because c++ is too dumb to call virtual methods from - /// inside a constructor - void init(); + surface_type_t type() const { return _stype; } + uint32_t number() const { return _number; } + + MackieControlProtocol& mcp() const { return _mcp; } + + bool active() const { return _active; } + void drop_routes (); typedef std::vector<Control*> Controls; - - /// This collection has ownership of all the controls Controls controls; - /** - These are alternative addressing schemes - They use maps because the indices aren't always - 0-based. - - Indexed by raw_id not by id. @see Control for the distinction. - */ std::map<int,Fader*> faders; std::map<int,Pot*> pots; std::map<int,Button*> buttons; @@ -75,39 +57,63 @@ public: /// no strip controls in here because they usually /// have the same names. std::map<std::string,Control*> controls_by_name; + + Mackie::JogWheel* jog_wheel() const { return _jog_wheel; } /// The collection of all numbered strips. No master /// strip in here. typedef std::vector<Strip*> Strips; Strips strips; + uint32_t n_strips () const; + Strip* nth_strip (uint32_t n) const; + /// This collection owns the groups typedef std::map<std::string,Group*> Groups; Groups groups; - uint32_t max_strips() const { return _max_strips; } - -public: + SurfacePort& port() const { return *_port; } + + const MidiByteArray& sysex_hdr() const; + + void periodic (); + + void handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t, uint32_t channel_id); + void handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes*); + void handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes*); + + /// Connect the any signal from the parser to handle_midi_any + /// unless it's already connected + void connect_to_signals (); + + /// notification from a MackiePort that it's now inactive + void handle_port_inactive(Mackie::SurfacePort *); + + /// write a sysex message + void write_sysex (const MidiByteArray& mba); + void write_sysex (MIDI::byte msg); + /// proxy write for port + void write (const MidiByteArray&); + /// display an indicator of the first switched-in Route. Do nothing by default. - virtual void display_bank_start( SurfacePort &, MackieMidiBuilder &, uint32_t /*current_bank*/ ) {}; + void display_bank_start (uint32_t /*current_bank*/); - /// called from MackieControlPRotocol::zero_all to turn things off - virtual void zero_all( SurfacePort &, MackieMidiBuilder & ) {}; + /// called from MackieControlProtocol::zero_all to turn things off + void zero_all (); /// turn off leds around the jog wheel. This is for surfaces that use a pot /// pretending to be a jog wheel. - virtual void blank_jog_ring( SurfacePort &, MackieMidiBuilder & ) {}; + void blank_jog_ring (); + + bool has_timecode_display() const; + void display_timecode (const std::string & /*timecode*/, const std::string & /*timecode_last*/); - virtual bool has_timecode_display() const = 0; - virtual void display_timecode( SurfacePort &, MackieMidiBuilder &, const std::string & /*timecode*/, const std::string & /*timecode_last*/) {}; - -public: /** This is used to calculate the clicks per second that define a transport speed of 1.0 for the jog wheel. 100.0 is 10 clicks per second, 50.5 is 5 clicks per second. */ - virtual float scrub_scaling_factor() = 0; + float scrub_scaling_factor() const; /** The scaling factor function for speed increase and decrease. At @@ -116,14 +122,25 @@ public: high definition control at low speeds and quick speed changes to/from higher speeds. */ - virtual float scaled_delta( const ControlState & state, float current_speed ) = 0; + float scaled_delta (const ControlState & state, float current_speed); + + void handle_control_event (Mackie::Control & control, const Mackie::ControlState & state); + + protected: + void init_controls(); + void init_strips (); -protected: - virtual void init_controls(); - virtual void init_strips (); + private: + MackieControlProtocol& _mcp; + SurfacePort* _port; + surface_type_t _stype; + uint32_t _number; + bool _active; + bool _connected; + Mackie::JogWheel* _jog_wheel; + MackieMidiBuilder builder; - const uint32_t _max_strips; - const uint32_t _unit_strips; + void jog_wheel_state_display (Mackie::JogWheel::State state); }; } diff --git a/libs/surfaces/mackie/surface_port.cc b/libs/surfaces/mackie/surface_port.cc index 5a336447c8..5fcea4c98f 100644 --- a/libs/surfaces/mackie/surface_port.cc +++ b/libs/surfaces/mackie/surface_port.cc @@ -15,49 +15,48 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include "surface_port.h" -#include "mackie_control_exception.h" -#include "controls.h" +#include <sstream> +#include <cstring> +#include <cerrno> + +#include <sigc++/sigc++.h> +#include <boost/shared_array.hpp> #include "midi++/types.h" #include "midi++/port.h" #include "midi++/manager.h" -#include <sigc++/sigc++.h> -#include <boost/shared_array.hpp> -#include "i18n.h" +#include "ardour/debug.h" +#include "ardour/rc_configuration.h" -#include <sstream> +#include "controls.h" +#include "mackie_control_exception.h" +#include "surface.h" +#include "surface_port.h" -#include <cstring> -#include <cerrno> + +#include "i18n.h" using namespace std; using namespace Mackie; - -SurfacePort::SurfacePort() - : _input_port (0), _output_port (0), _number (0), _active (false) -{ -} +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. */ -SurfacePort::SurfacePort (MIDI::Port & input_port, MIDI::Port & output_port, int number) - : _input_port (&input_port), _output_port (&output_port), _number (number), _active (false) +SurfacePort::SurfacePort (Surface& s, MIDI::Port & input_port, MIDI::Port & output_port) + : _surface (&s) + , _input_port (&input_port) + , _output_port (&output_port) + , _active (false) { } SurfacePort::~SurfacePort() { -#ifdef PORT_DEBUG - cout << "~SurfacePort::SurfacePort()" << endl; -#endif - // make sure another thread isn't reading or writing as we close the port - Glib::RecMutex::Lock lock (_rwlock); - _active = false; + close (); MIDI::Manager* mm = MIDI::Manager::instance (); @@ -70,10 +69,6 @@ SurfacePort::~SurfacePort() mm->remove_port (_output_port); delete _output_port; } - -#ifdef PORT_DEBUG - cout << "~SurfacePort::SurfacePort() finished" << endl; -#endif } // wrapper for one day when strerror_r is working properly @@ -96,18 +91,7 @@ MidiByteArray SurfacePort::read() } // return nothing read if the lock isn't acquired -#if 0 - Glib::RecMutex::Lock lock (_rwlock, Glib::TRY_LOCK); - - if (!lock.locked()) { - cout << "SurfacePort::read not locked" << endl; - return retval; - } - - // check active again - destructor sequence - if (!active()) return retval; -#endif - + // read port and copy to return value int nread = input_port().read (buf, sizeof (buf)); @@ -150,8 +134,6 @@ void SurfacePort::write (const MidiByteArray & mba) // that the destructor doesn't destroy the mutex while // it's still in use if (!active()) return; - Glib::RecMutex::Lock lock (_rwlock); - if (!active()) return; int count = output_port().write (mba.bytes().get(), mba.size(), 0); if (count != (int)mba.size()) { @@ -171,24 +153,114 @@ void SurfacePort::write (const MidiByteArray & mba) #endif } -void SurfacePort::write_sysex (const MidiByteArray & mba) + +void SurfacePort::open() { - if (mba.empty()) { - return; + 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); + } + + _active = false; +} + +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; } - MidiByteArray buf; - buf << sysex_hdr() << mba << MIDI::eox; - write (buf); + // 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; } -void SurfacePort::write_sysex (MIDI::byte msg) +// not used right now +MidiByteArray SurfacePort::host_connection_confirmation (const MidiByteArray & bytes) { - MidiByteArray buf; - buf << sysex_hdr() << msg << MIDI::eox; - write (buf); + 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) { os << "{ "; diff --git a/libs/surfaces/mackie/surface_port.h b/libs/surfaces/mackie/surface_port.h index 63966c6f3a..8b66073b4a 100644 --- a/libs/surfaces/mackie/surface_port.h +++ b/libs/surfaces/mackie/surface_port.h @@ -18,6 +18,7 @@ #ifndef surface_port_h #define surface_port_h +#include <midi++/types.h> #include <glibmm/thread.h> #include "pbd/signals.h" @@ -26,82 +27,62 @@ namespace MIDI { class Port; + class Parser; } +class MackieControlProtocol; + namespace Mackie { +class Surface; + /** Make a relationship between a midi port and a Mackie device. */ -class SurfacePort : public PBD::ScopedConnectionList + +class SurfacePort { public: - SurfacePort (MIDI::Port & input_port, MIDI::Port & output_port, int number); + SurfacePort (Mackie::Surface&, MIDI::Port& input_port, MIDI::Port& output_port); virtual ~SurfacePort(); - // when this is successful, active() should return true - virtual void open() = 0; - - // subclasses should call this before doing their own close - virtual void close() = 0; + 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 - virtual MidiByteArray read(); + MidiByteArray read(); /// an easier way to output bytes via midi - virtual void write( const MidiByteArray & ); + void write (const MidiByteArray&); - /// write a sysex message - void write_sysex( const MidiByteArray & mba ); - void write_sysex( MIDI::byte msg ); - - /// return the correct sysex header for this port - virtual const MidiByteArray & sysex_hdr() const = 0; - - 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 just before the port goes into initialisation - // where it tries to establish that its device is connected - PBD::Signal0<void> init_event; - - // emitted when the port completes initialisation successfully - PBD::Signal0<void> active_event; + 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; - // the port number - master is 0(extenders are 1((,4 - virtual int number() const { return _number; } - - // number of strips handled by this port. Usually 8. - virtual int strips() const = 0; + void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count); - virtual bool active() const { return _active; } - virtual void active( bool yn ) { _active = yn; } + bool active() const { return _active; } - void add_in_use_timeout (Control &, Control *); - protected: - /// Only for use by DummyPort - SurfacePort(); + MidiByteArray host_connection_query (MidiByteArray& bytes); + MidiByteArray host_connection_confirmation (const MidiByteArray& bytes); - virtual void control_event (SurfacePort &, Control &, const ControlState &) {} - private: - MIDI::Port * _input_port; - MIDI::Port * _output_port; - int _number; - bool _active; + Mackie::Surface* _surface; + MIDI::Port* _input_port; + MIDI::Port* _output_port; + bool _active; - Glib::RecMutex _rwlock; + PBD::ScopedConnection sysex_connection; }; -std::ostream & operator << ( std::ostream & , const SurfacePort & port ); +std::ostream& operator << (std::ostream& , const SurfacePort& port); } diff --git a/libs/surfaces/mackie/types.h b/libs/surfaces/mackie/types.h index be5c7e8b79..4fc52f66e4 100644 --- a/libs/surfaces/mackie/types.h +++ b/libs/surfaces/mackie/types.h @@ -23,6 +23,11 @@ namespace Mackie { +enum surface_type_t { + mcu, + ext, +}; + /** This started off as an enum, but it got really annoying typing ? on : off diff --git a/libs/surfaces/mackie/wscript b/libs/surfaces/mackie/wscript index 2d0479ef18..9aac5ce811 100644 --- a/libs/surfaces/mackie/wscript +++ b/libs/surfaces/mackie/wscript @@ -21,23 +21,17 @@ def configure(conf): def build(bld): obj = bld(features = 'cxx cxxshlib') obj.source = ''' - bcf_surface.cc button.cc controls.cc - dummy_port.cc fader.cc gui.cc interface.cc mackie_control_protocol.cc - mackie_control_protocol_poll.cc mackie_jog_wheel.cc mackie_midi_builder.cc - mackie_port.cc - mackie_surface.cc mcp_buttons.cc meter.cc midi_byte_array.cc - route_signal.cc strip.cc surface.cc surface_port.cc |