diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index f50be8fd37..285d9a5984 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -87,6 +87,7 @@ namespace PBD { LIBARDOUR_API extern DebugBits ProcessThreads; LIBARDOUR_API extern DebugBits Processors; LIBARDOUR_API extern DebugBits Push2; + LIBARDOUR_API extern DebugBits MIDISurface; LIBARDOUR_API extern DebugBits Selection; LIBARDOUR_API extern DebugBits SessionEvents; LIBARDOUR_API extern DebugBits Slave; diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index 35e8ce15a1..49af031840 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -82,6 +82,7 @@ PBD::DebugBits PBD::DEBUG::Ports = PBD::new_debug_bit ("Ports"); PBD::DebugBits PBD::DEBUG::ProcessThreads = PBD::new_debug_bit ("processthreads"); PBD::DebugBits PBD::DEBUG::Processors = PBD::new_debug_bit ("processors"); PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2"); +PBD::DebugBits PBD::DEBUG::MIDISurface = PBD::new_debug_bit ("midisurface"); PBD::DebugBits PBD::DEBUG::Selection = PBD::new_debug_bit ("selection"); PBD::DebugBits PBD::DEBUG::SessionEvents = PBD::new_debug_bit ("sessionevents"); PBD::DebugBits PBD::DEBUG::Slave = PBD::new_debug_bit ("slave"); diff --git a/libs/surfaces/midi_surface/midi_surface.cc b/libs/surfaces/midi_surface/midi_surface.cc index 56220efcc1..2388cdaa1e 100644 --- a/libs/surfaces/midi_surface/midi_surface.cc +++ b/libs/surfaces/midi_surface/midi_surface.cc @@ -33,11 +33,13 @@ using namespace ARDOUR; using namespace Glib; using namespace PBD; -MIDISurface::MIDISurface (ARDOUR::Session& s, std::string const & namestr, bool use_pad_filter) +MIDISurface::MIDISurface (ARDOUR::Session& s, std::string const & namestr, std::string const & port_prefix, bool use_pad_filter) : ControlProtocol (s, namestr) , AbstractUI (namestr) , with_pad_filter (use_pad_filter) , _in_use (false) + , port_name_prefix (port_prefix) + , _connection_state (ConnectionState (0)) { ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::port_registration_handler, this), this); @@ -52,8 +54,8 @@ MIDISurface::ports_acquire () /* setup ports */ - _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Push 2 in"), true); - _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true); + _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, string_compose (X_("%1 in"), port_name_prefix), true); + _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, string_compose (X_("%1 out"), port_name_prefix), true); if (_async_in == 0 || _async_out == 0) { DEBUG_TRACE (DEBUG::MIDISurface, "cannot register ports\n"); @@ -74,7 +76,7 @@ MIDISurface::ports_acquire () */ if (with_pad_filter) { - boost::dynamic_pointer_cast(_async_in)->add_shadow_port (string_compose (_("%1 Pads"), X_("Push 2")), boost::bind (&MIDISurface::pad_filter, this, _1, _2)); + boost::dynamic_pointer_cast(_async_in)->add_shadow_port (string_compose (_("%1 Pads"), port_name_prefix), boost::bind (&MIDISurface::pad_filter, this, _1, _2)); boost::shared_ptr shadow_port = boost::dynamic_pointer_cast(_async_in)->shadow_port(); if (shadow_port) { @@ -378,12 +380,25 @@ int MIDISurface::begin_using_device () { _in_use = true; + connect_session_signals (); return 0; } int MIDISurface::stop_using_device () { + session_connections.drop_connections (); _in_use = false; return 0; } + +void +MIDISurface::drop () +{ + /* do this before stopping the event loop, so that we don't get any notifications */ + port_connections.drop_connections (); + + stop_using_device (); + device_release (); + ports_release (); +} diff --git a/libs/surfaces/midi_surface/midi_surface.h b/libs/surfaces/midi_surface/midi_surface.h index 280aa83870..ba751596ec 100644 --- a/libs/surfaces/midi_surface/midi_surface.h +++ b/libs/surfaces/midi_surface/midi_surface.h @@ -44,7 +44,7 @@ class MIDISurface : public ARDOUR::ControlProtocol , public AbstractUI { public: - MIDISurface (ARDOUR::Session&, std::string const & name, bool use_pad_filter); + MIDISurface (ARDOUR::Session&, std::string const & name, std::string const & port_name_prefix, bool use_pad_filter); ~MIDISurface (); boost::shared_ptr input_port(); @@ -71,9 +71,10 @@ class MIDISurface : public ARDOUR::ControlProtocol CONTROL_PROTOCOL_THREADS_NEED_TEMPO_MAP_DECL(); - private: + protected: bool with_pad_filter; bool _in_use; + std::string port_name_prefix; MIDI::Port* _input_port; MIDI::Port* _output_port; @@ -122,4 +123,6 @@ class MIDISurface : public ARDOUR::ControlProtocol virtual int stop_using_device (); virtual int device_acquire () = 0; virtual void device_release () = 0; + + void drop (); }; diff --git a/libs/surfaces/push2/push2.cc b/libs/surfaces/push2/push2.cc index 183320b2df..2538ae7102 100644 --- a/libs/surfaces/push2/push2.cc +++ b/libs/surfaces/push2/push2.cc @@ -97,15 +97,12 @@ row_interval_semitones (const Push2::RowInterval row_interval, const bool inkey) } Push2::Push2 (ARDOUR::Session& s) - : ControlProtocol (s, std::string (X_("Ableton Push 2"))) - , AbstractUI (name()) + : MIDISurface (s, X_("Ableton Push 2"), X_("Push 2"), true) , _handle (0) - , _in_use (false) , _modifier_state (None) , _splash_start (0) , _current_layout (0) , _previous_layout (0) - , _connection_state (ConnectionState (0)) , _gui (0) , _mode (MusicalMode::IonianMajor) , _row_interval (Fourth) @@ -141,31 +138,13 @@ Push2::Push2 (ARDOUR::Session& s) _splash_layout = new SplashLayout (*this, *session, "splash"); run_event_loop (); - - /* Ports exist for the life of this instance */ - - ports_acquire (); - - /* catch arrival and departure of Push2 itself */ - ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this); - - /* Catch port connections and disconnections */ - ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&Push2::connection_handler, this, _1, _2, _3, _4, _5), this); - - /* Push 2 ports might already be there */ - port_registration_handler (); } Push2::~Push2 () { DEBUG_TRACE (DEBUG::Push2, "push2 control surface object being destroyed\n"); - /* do this before stopping the event loop, so that we don't get any notifications */ - port_connections.drop_connections (); - - stop_using_device (); - device_release (); - ports_release (); + MIDISurface::drop (); if (_current_layout) { _canvas->root()->remove (_current_layout); @@ -206,6 +185,7 @@ Push2::begin_using_device () { DEBUG_TRACE (DEBUG::Push2, "begin using device\n"); + /* set up periodic task used to push a frame buffer to the * device (25fps). The device can handle 60fps, but we don't * need that frame rate. @@ -215,8 +195,6 @@ Push2::begin_using_device () _vblank_connection = vblank_timeout->connect (sigc::mem_fun (*this, &Push2::vblank)); vblank_timeout->attach (main_loop()->get_context()); - connect_session_signals (); - init_buttons (true); init_touch_strip (); reset_pad_colors (); @@ -227,9 +205,7 @@ Push2::begin_using_device () request_pressure_mode (); - _in_use = true; - - return 0; + return MIDISurface::begin_using_device (); } int @@ -252,89 +228,8 @@ Push2::stop_using_device () } _vblank_connection.disconnect (); - session_connections.drop_connections (); - _in_use = false; - return 0; -} - -int -Push2::ports_acquire () -{ - DEBUG_TRACE (DEBUG::Push2, "acquiring ports\n"); - - /* setup ports */ - - _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Push 2 in"), true); - _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true); - - if (_async_in == 0 || _async_out == 0) { - DEBUG_TRACE (DEBUG::Push2, "cannot register ports\n"); - return -1; - } - - /* We do not add our ports to the input/output bundles because we don't - * want users wiring them by hand. They could use JACK tools if they - * really insist on that (and use JACK) - */ - - _input_port = boost::dynamic_pointer_cast(_async_in).get(); - _output_port = boost::dynamic_pointer_cast(_async_out).get(); - - /* Create a shadow port where, depending on the state of the surface, - * we will make pad note on/off events appear. The surface code will - * automatically this port to the first selected MIDI track. - */ - - boost::dynamic_pointer_cast(_async_in)->add_shadow_port (string_compose (_("%1 Pads"), X_("Push 2")), boost::bind (&Push2::pad_filter, this, _1, _2)); - boost::shared_ptr shadow_port = boost::dynamic_pointer_cast(_async_in)->shadow_port(); - - if (shadow_port) { - - _output_bundle.reset (new ARDOUR::Bundle (_("Push 2 Pads"), false)); - - _output_bundle->add_channel ( - shadow_port->name(), - ARDOUR::DataType::MIDI, - session->engine().make_port_name_non_relative (shadow_port->name()) - ); - } - - session->BundleAddedOrRemoved (); - - connect_to_parser (); - - /* Connect input port to event loop */ - - AsyncMIDIPort* asp; - - asp = dynamic_cast (_input_port); - asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &Push2::midi_input_handler), _input_port)); - asp->xthread().attach (main_loop()->get_context()); - - return 0; -} - -void -Push2::ports_release () -{ - DEBUG_TRACE (DEBUG::Push2, "releasing ports\n"); - - /* wait for button data to be flushed */ - AsyncMIDIPort* asp; - asp = dynamic_cast (_output_port); - asp->drain (10000, 500000); - - { - Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock()); - AudioEngine::instance()->unregister_port (_async_in); - AudioEngine::instance()->unregister_port (_async_out); - } - - _async_in.reset ((ARDOUR::Port*) 0); - _async_out.reset ((ARDOUR::Port*) 0); - _input_port = 0; - _output_port = 0; + return MIDISurface::stop_using_device (); } int @@ -369,6 +264,7 @@ void Push2::device_release () { DEBUG_TRACE (DEBUG::Push2, "releasing device\n"); + if (_handle) { libusb_release_interface (_handle, 0x00); libusb_close (_handle); @@ -376,18 +272,6 @@ Push2::device_release () } } -list > -Push2::bundles () -{ - list > b; - - if (_output_bundle) { - b.push_back (_output_bundle); - } - - return b; -} - void Push2::strip_buttons_off () { @@ -474,19 +358,6 @@ Push2::request_factory (uint32_t num_requests) return request_buffer_factory (num_requests); } -void -Push2::do_request (Push2Request * req) -{ - if (req->type == CallSlot) { - - call_slot (MISSING_INVALIDATOR, req->the_slot); - - } else if (req->type == Quit) { - - stop_using_device (); - } -} - void Push2::splash () { @@ -566,59 +437,6 @@ Push2::init_touch_strip () write (msg); } -void -Push2::write (const MidiByteArray& data) -{ - /* immediate delivery */ - _output_port->write (&data[0], data.size(), 0); -} - -bool -Push2::midi_input_handler (IOCondition ioc, MIDI::Port* port) -{ - if (ioc & ~IO_IN) { - DEBUG_TRACE (DEBUG::Push2, "MIDI port closed\n"); - return false; - } - - if (ioc & IO_IN) { - - DEBUG_TRACE (DEBUG::Push2, string_compose ("something happened on %1\n", port->name())); - - AsyncMIDIPort* asp = dynamic_cast(port); - if (asp) { - asp->clear (); - } - - DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name())); - if (_in_use) { - samplepos_t now = AudioEngine::instance()->sample_time(); - port->parse (now); - } - } - - return true; -} - -void -Push2::connect_to_parser () -{ - DEBUG_TRACE (DEBUG::Push2, string_compose ("Connecting to signals on port %2\n", _input_port->name())); - - MIDI::Parser* p = _input_port->parser(); - - /* Incoming sysex */ - p->sysex.connect_same_thread (*this, boost::bind (&Push2::handle_midi_sysex, this, _1, _2, _3)); - /* V-Pot messages are Controller */ - p->controller.connect_same_thread (*this, boost::bind (&Push2::handle_midi_controller_message, this, _1, _2)); - /* Button messages are NoteOn */ - p->note_on.connect_same_thread (*this, boost::bind (&Push2::handle_midi_note_on_message, this, _1, _2)); - /* Button messages are NoteOn but libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */ - p->note_off.connect_same_thread (*this, boost::bind (&Push2::handle_midi_note_on_message, this, _1, _2)); - /* Fader messages are Pitchbend */ - p->channel_pitchbend[0].connect_same_thread (*this, boost::bind (&Push2::handle_midi_pitchbend_message, this, _1, _2)); -} - void Push2::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz) { @@ -884,37 +702,6 @@ Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb) { } -void -Push2::thread_init () -{ - pthread_set_name (event_loop_name().c_str()); - - PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048); - ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128); - - set_thread_priority (); -} - -void -Push2::connect_session_signals() -{ - // receive routes added - //session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MackieControlProtocol::notify_routes_added, this, _1), this); - // receive VCAs added - //session->vca_manager().VCAAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_vca_added, this, _1), this); - - // receive record state toggled - session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_record_state_changed, this), this); - // receive transport state changed - session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_transport_state_changed, this), this); - session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_loop_state_changed, this), this); - // receive punch-in and punch-out - Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_parameter_changed, this, _1), this); - session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_parameter_changed, this, _1), this); - // receive rude solo changed - session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_solo_active_changed, this, _1), this); -} - void Push2::notify_record_state_changed () { @@ -1014,15 +801,7 @@ Push2::notify_solo_active_changed (bool yn) XMLNode& Push2::get_state() const { - XMLNode& node (ControlProtocol::get_state()); - XMLNode* child; - - child = new XMLNode (X_("Input")); - child->add_child_nocopy (_async_in->get_state()); - node.add_child_nocopy (*child); - child = new XMLNode (X_("Output")); - child->add_child_nocopy (_async_out->get_state()); - node.add_child_nocopy (*child); + XMLNode& node (MIDISurface::get_state()); node.set_property (X_("root"), _scale_root); node.set_property (X_("root-octave"), _root_octave); @@ -1039,28 +818,10 @@ Push2::set_state (const XMLNode & node, int version) int retval = 0; - if (ControlProtocol::set_state (node, version)) { + if (MIDISurface::set_state (node, version)) { return -1; } - XMLNode* child; - - if ((child = node.child (X_("Input"))) != 0) { - XMLNode* portnode = child->child (Port::state_node_name.c_str()); - if (portnode) { - portnode->remove_property ("name"); - _async_in->set_state (*portnode, version); - } - } - - if ((child = node.child (X_("Output"))) != 0) { - XMLNode* portnode = child->child (Port::state_node_name.c_str()); - if (portnode) { - portnode->remove_property ("name"); - _async_out->set_state (*portnode, version); - } - } - node.get_property (X_("root"), _scale_root); node.get_property (X_("root-octave"), _root_octave); node.get_property (X_("in-key"), _in_key); @@ -1199,117 +960,30 @@ Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const return matched; } -void -Push2::port_registration_handler () +std::string +Push2::input_port_name () const { - if (!_async_in || !_async_out) { - /* ports not registered yet */ - return; - } - - if (_async_in->connected() && _async_out->connected()) { - /* don't waste cycles here */ - return; - } - #ifdef __APPLE__ /* the origin of the numeric magic identifiers is known only to Ableton and may change in time. This is part of how CoreMIDI works. */ - std::string input_port_name = X_("system:midi_capture_1319078870"); - std::string output_port_name = X_("system:midi_playback_3409210341"); + return X_("system:midi_capture_1319078870"); #else - std::string input_port_name = X_("Ableton Push 2 MIDI 1 in"); - std::string output_port_name = X_("Ableton Push 2 MIDI 1 out"); + return X_("Ableton Push 2 MIDI 1 in"); #endif - std::vector in; - std::vector out; - - AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in); - AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name), DataType::MIDI, PortFlags (IsPhysical|IsInput), out); - - if (!in.empty() && !out.empty()) { - if (!_async_in->connected()) { - AudioEngine::instance()->connect (_async_in->name(), in.front()); - } - if (!_async_out->connected()) { - AudioEngine::instance()->connect (_async_out->name(), out.front()); - } - } } -bool -Push2::connection_handler (boost::weak_ptr, std::string name1, boost::weak_ptr, std::string name2, bool yn) +std::string +Push2::output_port_name () const { - DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n"); - if (!_input_port || !_output_port) { - return false; - } - - std::string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr(_async_in)->name()); - std::string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr(_async_out)->name()); - - if (ni == name1 || ni == name2) { - if (yn) { - _connection_state |= InputConnected; - } else { - _connection_state &= ~InputConnected; - } - } else if (no == name1 || no == name2) { - if (yn) { - _connection_state |= OutputConnected; - } else { - _connection_state &= ~OutputConnected; - } - } else { - DEBUG_TRACE (DEBUG::Push2, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2)); - /* not our ports */ - return false; - } - - DEBUG_TRACE (DEBUG::Push2, string_compose ("our ports changed connection state: %1 -> %2 connected ? %3\n", - name1, name2, yn)); - - if ((_connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) { - - /* XXX this is a horrible hack. Without a short sleep here, - something prevents the device wakeup messages from being - sent and/or the responses from being received. - */ - - g_usleep (100000); - DEBUG_TRACE (DEBUG::Push2, "device now connected for both input and output\n"); - - /* may not have the device open if it was just plugged - in. Really need USB device detection rather than MIDI port - detection for this to work well. - */ - - device_acquire (); - begin_using_device (); - - } else { - DEBUG_TRACE (DEBUG::FaderPort, "Device disconnected (input or output or both) or not yet fully connected\n"); - stop_using_device (); - } - - ConnectionChange (); /* emit signal for our GUI */ - - DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler end\n"); - - return true; /* connection status changed */ -} - -boost::shared_ptr -Push2::output_port() -{ - return _async_out; -} - -boost::shared_ptr -Push2::input_port() -{ - return _async_in; +#ifdef __APPLE__ + /* the origin of the numeric magic identifiers is known only to Ableton + and may change in time. This is part of how CoreMIDI works. + */ + return X_("system:midi_playback_3409210341"); +#else + return X_("Ableton Push 2 MIDI 1 out"); +#endif } int diff --git a/libs/surfaces/push2/push2.h b/libs/surfaces/push2/push2.h index ec5632129d..fe18aa38c3 100644 --- a/libs/surfaces/push2/push2.h +++ b/libs/surfaces/push2/push2.h @@ -40,7 +40,8 @@ #include "gtkmm2ext/colors.h" -#include "midi_byte_array.h" +#include "midi_surface/midi_byte_array.h" +#include "midi_surface/midi_surface.h" namespace MIDI { class Parser; @@ -55,18 +56,11 @@ namespace ARDOUR { namespace ArdourSurface { -struct Push2Request : public BaseUI::BaseRequestObject { -public: - Push2Request () {} - ~Push2Request () {} -}; - class P2GUI; class Push2Layout; class Push2Canvas; -class Push2 : public ARDOUR::ControlProtocol - , public AbstractUI +class Push2 : public MIDISurface { public: enum ButtonID { @@ -305,7 +299,8 @@ class Push2 : public ARDOUR::ControlProtocol static bool probe (); static void* request_factory (uint32_t); - std::list > bundles (); + std::string input_port_name () const; + std::string output_port_name () const; bool has_editor () const { return true; } void* get_gui () const; @@ -315,11 +310,6 @@ class Push2 : public ARDOUR::ControlProtocol XMLNode& get_state() const; int set_state (const XMLNode & node, int version); - PBD::Signal0 ConnectionChange; - - boost::shared_ptr input_port(); - boost::shared_ptr output_port(); - int pad_note (int row, int col) const; PBD::Signal0 PadChange; @@ -438,8 +428,6 @@ class Push2 : public ARDOUR::ControlProtocol void strip_buttons_off (); - void write (const MidiByteArray&); - uint8_t get_color_index (Gtkmm2ext::Color rgba); Gtkmm2ext::Color get_color (ColorName); @@ -449,8 +437,6 @@ class Push2 : public ARDOUR::ControlProtocol libusb_device_handle* usb_handle() const { return _handle; } - ARDOUR::Session & get_session() { return *session; } - bool stop_down () const { return _stop_down; } typedef std::map > PadMap; @@ -459,21 +445,14 @@ class Push2 : public ARDOUR::ControlProtocol boost::shared_ptr pad_by_xy (int x, int y); boost::shared_ptr