13
0

reorganize push 2 code and logic to better handle device arrival after program startup

Note: we do not handle device departure correctly yet, mostly because the shadow (pad) port has a retained
reference somewhere
This commit is contained in:
Paul Davis 2016-09-30 11:22:30 -04:00
parent 8cff66c7e0
commit 1b830f2604
3 changed files with 226 additions and 177 deletions

View File

@ -71,7 +71,6 @@ Push2Canvas::vblank ()
/* re-render dirty areas, if any */ /* re-render dirty areas, if any */
if (expose ()) { if (expose ()) {
DEBUG_TRACE (DEBUG::Push2, "re-blit to device frame buffer\n");
/* something rendered, update device_frame_buffer */ /* something rendered, update device_frame_buffer */
blit_to_device_frame_buffer (); blit_to_device_frame_buffer ();

View File

@ -76,6 +76,7 @@ Push2::Push2 (ARDOUR::Session& s)
: ControlProtocol (s, string (X_("Ableton Push 2"))) : ControlProtocol (s, string (X_("Ableton Push 2")))
, AbstractUI<Push2Request> (name()) , AbstractUI<Push2Request> (name())
, handle (0) , handle (0)
, in_use (false)
, _modifier_state (None) , _modifier_state (None)
, splash_start (0) , splash_start (0)
, _current_layout (0) , _current_layout (0)
@ -93,6 +94,9 @@ Push2::Push2 (ARDOUR::Session& s)
, contrast_color (LED::Green) , contrast_color (LED::Green)
, in_range_select (false) , in_range_select (false)
{ {
/* we're going to need this */
libusb_init (NULL);
build_maps (); build_maps ();
build_color_map (); build_color_map ();
@ -101,17 +105,21 @@ Push2::Push2 (ARDOUR::Session& s)
/* master cannot be removed, so no need to connect to going-away signal */ /* master cannot be removed, so no need to connect to going-away signal */
master = session->master_out (); master = session->master_out ();
if (open ()) {
throw failed_constructor ();
}
ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this); ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
/* catch current selection, if any */ /* allocate graphics layouts, even though we're not using them yet */
{
StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected())); _canvas = new Push2Canvas (*this, 960, 160);
stripable_selection_change (sp); mix_layout = new MixLayout (*this, *session, "globalmix");
} scale_layout = new ScaleLayout (*this, *session, "scale");
track_mix_layout = new TrackMixLayout (*this, *session, "trackmix");
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 */ /* catch arrival and departure of Push2 itself */
ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this); ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this);
@ -119,83 +127,103 @@ Push2::Push2 (ARDOUR::Session& s)
/* Catch port connections and disconnections */ /* Catch port connections and disconnections */
ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&Push2::connection_handler, this, _1, _2, _3, _4, _5), this); ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&Push2::connection_handler, this, _1, _2, _3, _4, _5), this);
/* ports might already be there */ /* Push 2 ports might already be there */
port_registration_handler (); port_registration_handler ();
} }
Push2::~Push2 () Push2::~Push2 ()
{ {
stop (); selection_connection.disconnect ();
stop_event_loop (); /* this will call stop_using_device () in Quit request handler */
device_release ();
ports_release ();
if (_current_layout) {
_canvas->root()->remove (_current_layout);
_current_layout = 0;
}
delete track_mix_layout;
delete mix_layout; delete mix_layout;
mix_layout = 0;
delete scale_layout; delete scale_layout;
scale_layout = 0;
delete splash_layout;
splash_layout = 0;
} }
void void
Push2::port_registration_handler () Push2::run_event_loop ()
{ {
if (!_async_in && !_async_out) { DEBUG_TRACE (DEBUG::Push2, "start event loop\n");
/* ports not registered yet */ BaseUI::run ();
return; }
}
if (_async_in->connected() && _async_out->connected()) { void
/* don't waste cycles here */ Push2::stop_event_loop ()
return; {
} DEBUG_TRACE (DEBUG::Push2, "stop event loop\n");
BaseUI::quit ();
string input_port_name = X_("Ableton Push 2 MIDI 1 in");
string output_port_name = X_("Ableton Push 2 MIDI 1 out");
vector<string> in;
vector<string> 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()) {
cerr << "Push2: both ports found\n";
cerr << "\tconnecting to " << in.front() << " + " << out.front() << endl;
if (!_async_in->connected()) {
AudioEngine::instance()->connect (_async_in->name(), in.front());
}
if (!_async_out->connected()) {
AudioEngine::instance()->connect (_async_out->name(), out.front());
}
}
} }
int int
Push2::open () Push2::begin_using_device ()
{ {
int err; DEBUG_TRACE (DEBUG::Push2, "begin using device\n");
if (handle) { /* set up periodic task used to push a frame buffer to the
/* already open */ * device (25fps). The device can handle 60fps, but we don't
* need that frame rate.
*/
Glib::RefPtr<Glib::TimeoutSource> vblank_timeout = Glib::TimeoutSource::create (40); // milliseconds
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 ();
set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
splash ();
/* catch current selection, if any so that we can wire up the pads if appropriate */
{
StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
stripable_selection_change (sp);
}
request_pressure_mode ();
in_use = true;
return 0;
}
int
Push2::stop_using_device ()
{
DEBUG_TRACE (DEBUG::Push2, "stop using device\n");
if (!in_use) {
DEBUG_TRACE (DEBUG::Push2, "nothing to do, device not in use\n");
return 0; return 0;
} }
if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) { init_buttons (false);
return -1; strip_buttons_off ();
}
if ((err = libusb_claim_interface (handle, 0x00))) { vblank_connection.disconnect ();
return -1; session_connections.drop_connections ();
}
try { in_use = false;
_canvas = new Push2Canvas (*this, 960, 160); return 0;
mix_layout = new MixLayout (*this, *session, "globalmix"); }
scale_layout = new ScaleLayout (*this, *session, "scale");
track_mix_layout = new TrackMixLayout (*this, *session, "trackmix"); int
splash_layout = new SplashLayout (*this, *session, "splash"); Push2::ports_acquire ()
} catch (...) { {
error << _("Cannot construct Canvas for display") << endmsg; DEBUG_TRACE (DEBUG::Push2, "acquiring ports\n");
libusb_release_interface (handle, 0x00);
libusb_close (handle);
handle = 0;
return -1;
}
/* setup ports */ /* setup ports */
@ -203,12 +231,13 @@ Push2::open ()
_async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true); _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true);
if (_async_in == 0 || _async_out == 0) { if (_async_in == 0 || _async_out == 0) {
DEBUG_TRACE (DEBUG::Push2, "cannot register ports\n");
return -1; return -1;
} }
/* We do not add our ports to the input/output bundles because we don't /* 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 * want users wiring them by hand. They could use JACK tools if they
* really insist on that. * really insist on that (and use JACK)
*/ */
_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get(); _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
@ -237,26 +266,21 @@ Push2::open ()
connect_to_parser (); connect_to_parser ();
/* Connect input port to event loop */
AsyncMIDIPort* asp;
asp = dynamic_cast<AsyncMIDIPort*> (_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; return 0;
} }
list<boost::shared_ptr<ARDOUR::Bundle> > void
Push2::bundles () Push2::ports_release ()
{ {
list<boost::shared_ptr<ARDOUR::Bundle> > b; DEBUG_TRACE (DEBUG::Push2, "releasing ports\n");
if (_output_bundle) {
b.push_back (_output_bundle);
}
return b;
}
int
Push2::close ()
{
init_buttons (false);
strip_buttons_off ();
/* wait for button data to be flushed */ /* wait for button data to be flushed */
AsyncMIDIPort* asp; AsyncMIDIPort* asp;
@ -270,29 +294,57 @@ Push2::close ()
_async_out.reset ((ARDOUR::Port*) 0); _async_out.reset ((ARDOUR::Port*) 0);
_input_port = 0; _input_port = 0;
_output_port = 0; _output_port = 0;
}
periodic_connection.disconnect (); int
session_connections.drop_connections (); Push2::device_acquire ()
{
int err;
if (_current_layout) { DEBUG_TRACE (DEBUG::Push2, "acquiring device\n");
_canvas->root()->remove (_current_layout);
_current_layout = 0; if (handle) {
DEBUG_TRACE (DEBUG::Push2, "open() called with handle already set\n");
/* already open */
return 0;
} }
delete mix_layout; if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
mix_layout = 0; DEBUG_TRACE (DEBUG::Push2, "failed to open USB handle\n");
delete scale_layout; return -1;
scale_layout = 0; }
delete splash_layout;
splash_layout = 0;
if ((err = libusb_claim_interface (handle, 0x00))) {
DEBUG_TRACE (DEBUG::Push2, "failed to claim USB device\n");
libusb_close (handle);
handle = 0;
return -1;
}
return 0;
}
void
Push2::device_release ()
{
DEBUG_TRACE (DEBUG::Push2, "releasing device\n");
if (handle) { if (handle) {
libusb_release_interface (handle, 0x00); libusb_release_interface (handle, 0x00);
libusb_close (handle); libusb_close (handle);
handle = 0; handle = 0;
} }
}
return 0; list<boost::shared_ptr<ARDOUR::Bundle> >
Push2::bundles ()
{
list<boost::shared_ptr<ARDOUR::Bundle> > b;
if (_output_bundle) {
b.push_back (_output_bundle);
}
return b;
} }
void void
@ -366,16 +418,6 @@ Push2::init_buttons (bool startup)
bool bool
Push2::probe () Push2::probe ()
{ {
libusb_device_handle *h;
libusb_init (NULL);
if ((h = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
DEBUG_TRACE (DEBUG::Push2, "no Push2 device found\n");
return false;
}
libusb_close (h);
DEBUG_TRACE (DEBUG::Push2, "Push2 device located\n");
return true; return true;
} }
@ -399,19 +441,10 @@ Push2::do_request (Push2Request * req)
} else if (req->type == Quit) { } else if (req->type == Quit) {
stop (); stop_using_device ();
} }
} }
int
Push2::stop ()
{
BaseUI::quit ();
close ();
return 0;
}
void void
Push2::splash () Push2::splash ()
{ {
@ -454,49 +487,20 @@ Push2::set_active (bool yn)
if (yn) { if (yn) {
/* start event loop */ if (device_acquire ()) {
BaseUI::run ();
if (open ()) {
DEBUG_TRACE (DEBUG::Push2, "device open failed\n");
close ();
return -1; return -1;
} }
/* Connect input port to event loop */ if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
begin_using_device ();
AsyncMIDIPort* asp; } else {
/* begin_using_device () will get called once we're connected */
asp = dynamic_cast<AsyncMIDIPort*> (_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());
connect_session_signals ();
/* 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.
*/
Glib::RefPtr<Glib::TimeoutSource> vblank_timeout = Glib::TimeoutSource::create (40); // milliseconds
vblank_connection = vblank_timeout->connect (sigc::mem_fun (*this, &Push2::vblank));
vblank_timeout->attach (main_loop()->get_context());
Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (1000); // milliseconds
periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &Push2::periodic));
periodic_timeout->attach (main_loop()->get_context());
init_buttons (true);
init_touch_strip ();
set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
splash ();
} else { } else {
/* Control Protocol Manager never calls us with false, but
stop (); * insteads destroys us.
*/
} }
ControlProtocol::set_active (yn); ControlProtocol::set_active (yn);
@ -537,27 +541,23 @@ Push2::midi_input_handler (IOCondition ioc, MIDI::Port* port)
if (ioc & IO_IN) { if (ioc & IO_IN) {
// DEBUG_TRACE (DEBUG::Push2, string_compose ("something happend on %1\n", port->name())); DEBUG_TRACE (DEBUG::Push2, string_compose ("something happend on %1\n", port->name()));
AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*>(port); AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*>(port);
if (asp) { if (asp) {
asp->clear (); asp->clear ();
} }
//DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name())); DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name()));
framepos_t now = AudioEngine::instance()->sample_time(); if (in_use) {
port->parse (now); framepos_t now = AudioEngine::instance()->sample_time();
port->parse (now);
}
} }
return true; return true;
} }
bool
Push2::periodic ()
{
return true;
}
void void
Push2::connect_to_parser () Push2::connect_to_parser ()
{ {
@ -1166,10 +1166,51 @@ Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
return matched; return matched;
} }
void
Push2::port_registration_handler ()
{
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.
*/
string input_port_name = X_("system:midi_capture_1319078870");
string output_port_name = X_("system:midi_playback_3409210341");
#else
string input_port_name = X_("Ableton Push 2 MIDI 1 in");
string output_port_name = X_("Ableton Push 2 MIDI 1 out");
#endif
vector<string> in;
vector<string> 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()) {
cerr << "Push2: both ports found\n";
cerr << "\tconnecting to " << in.front() << " + " << out.front() << endl;
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 bool
Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn) Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
{ {
DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n"); DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n");
if (!_input_port || !_output_port) { if (!_input_port || !_output_port) {
return false; return false;
} }
@ -1190,11 +1231,14 @@ Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boo
connection_state &= ~OutputConnected; connection_state &= ~OutputConnected;
} }
} else { } else {
DEBUG_TRACE (DEBUG::FaderPort, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2)); DEBUG_TRACE (DEBUG::Push2, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
/* not our ports */ /* not our ports */
return false; 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)) { if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
/* XXX this is a horrible hack. Without a short sleep here, /* XXX this is a horrible hack. Without a short sleep here,
@ -1203,11 +1247,19 @@ Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boo
*/ */
g_usleep (100000); g_usleep (100000);
DEBUG_TRACE (DEBUG::FaderPort, "device now connected for both input and output\n"); DEBUG_TRACE (DEBUG::Push2, "device now connected for both input and output\n");
connected ();
/* 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 { } else {
DEBUG_TRACE (DEBUG::FaderPort, "Device disconnected (input or output or both) or not yet fully connected\n"); 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 */ ConnectionChange (); /* emit signal for our GUI */
@ -1217,12 +1269,6 @@ Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boo
return true; /* connection status changed */ return true; /* connection status changed */
} }
void
Push2::connected ()
{
request_pressure_mode ();
}
boost::shared_ptr<Port> boost::shared_ptr<Port>
Push2::output_port() Push2::output_port()
{ {

View File

@ -370,12 +370,19 @@ class Push2 : public ARDOUR::ControlProtocol
private: private:
libusb_device_handle *handle; libusb_device_handle *handle;
bool in_use;
ModifierState _modifier_state; ModifierState _modifier_state;
void do_request (Push2Request*); void do_request (Push2Request*);
int stop ();
int open (); int begin_using_device ();
int close (); int stop_using_device ();
int device_acquire ();
void device_release ();
int ports_acquire ();
void ports_release ();
void run_event_loop ();
void stop_event_loop ();
void relax () {} void relax () {}
@ -431,9 +438,6 @@ class Push2 : public ARDOUR::ControlProtocol
bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port); bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port);
sigc::connection periodic_connection;
bool periodic ();
void thread_init (); void thread_init ();
PBD::ScopedConnectionList session_connections; PBD::ScopedConnectionList session_connections;