X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fpush2%2Fpush2.cc;h=9f1290ec094deeb0e30392310d55c9947d7509e9;hb=1b830f2604f18149d88b3f2c34cb64660f46d7aa;hp=3fb5b6c372dbc8efbcee7686f5e68d3193082226;hpb=33015a7173ef790746a340cc39a1242a03dc015b;p=ardour.git diff --git a/libs/surfaces/push2/push2.cc b/libs/surfaces/push2/push2.cc index 3fb5b6c372..9f1290ec09 100644 --- a/libs/surfaces/push2/push2.cc +++ b/libs/surfaces/push2/push2.cc @@ -57,6 +57,10 @@ #include "pbd/i18n.h" +#ifdef PLATFORM_WINDOWS +#define random() rand() +#endif + using namespace ARDOUR; using namespace std; using namespace PBD; @@ -68,63 +72,11 @@ using namespace ArdourSurface; #define ABLETON 0x2982 #define PUSH2 0x1967 -__attribute__((constructor)) static void -register_enums () -{ - EnumWriter& enum_writer (EnumWriter::instance()); - vector i; - vector s; - - MusicalMode::Type mode; - -#define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear() -#define REGISTER_CLASS_ENUM(t,e) i.push_back (t::e); s.push_back (#e) - - REGISTER_CLASS_ENUM (MusicalMode,Dorian); - REGISTER_CLASS_ENUM (MusicalMode, IonianMajor); - REGISTER_CLASS_ENUM (MusicalMode, Minor); - REGISTER_CLASS_ENUM (MusicalMode, HarmonicMinor); - REGISTER_CLASS_ENUM (MusicalMode, MelodicMinorAscending); - REGISTER_CLASS_ENUM (MusicalMode, MelodicMinorDescending); - REGISTER_CLASS_ENUM (MusicalMode, Phrygian); - REGISTER_CLASS_ENUM (MusicalMode, Lydian); - REGISTER_CLASS_ENUM (MusicalMode, Mixolydian); - REGISTER_CLASS_ENUM (MusicalMode, Aeolian); - REGISTER_CLASS_ENUM (MusicalMode, Locrian); - REGISTER_CLASS_ENUM (MusicalMode, PentatonicMajor); - REGISTER_CLASS_ENUM (MusicalMode, PentatonicMinor); - REGISTER_CLASS_ENUM (MusicalMode, Chromatic); - REGISTER_CLASS_ENUM (MusicalMode, BluesScale); - REGISTER_CLASS_ENUM (MusicalMode, NeapolitanMinor); - REGISTER_CLASS_ENUM (MusicalMode, NeapolitanMajor); - REGISTER_CLASS_ENUM (MusicalMode, Oriental); - REGISTER_CLASS_ENUM (MusicalMode, DoubleHarmonic); - REGISTER_CLASS_ENUM (MusicalMode, Enigmatic); - REGISTER_CLASS_ENUM (MusicalMode, Hirajoshi); - REGISTER_CLASS_ENUM (MusicalMode, HungarianMinor); - REGISTER_CLASS_ENUM (MusicalMode, HungarianMajor); - REGISTER_CLASS_ENUM (MusicalMode, Kumoi); - REGISTER_CLASS_ENUM (MusicalMode, Iwato); - REGISTER_CLASS_ENUM (MusicalMode, Hindu); - REGISTER_CLASS_ENUM (MusicalMode, Spanish8Tone); - REGISTER_CLASS_ENUM (MusicalMode, Pelog); - REGISTER_CLASS_ENUM (MusicalMode, HungarianGypsy); - REGISTER_CLASS_ENUM (MusicalMode, Overtone); - REGISTER_CLASS_ENUM (MusicalMode, LeadingWholeTone); - REGISTER_CLASS_ENUM (MusicalMode, Arabian); - REGISTER_CLASS_ENUM (MusicalMode, Balinese); - REGISTER_CLASS_ENUM (MusicalMode, Gypsy); - REGISTER_CLASS_ENUM (MusicalMode, Mohammedan); - REGISTER_CLASS_ENUM (MusicalMode, Javanese); - REGISTER_CLASS_ENUM (MusicalMode, Persian); - REGISTER_CLASS_ENUM (MusicalMode, Algerian); - REGISTER (mode); -} - Push2::Push2 (ARDOUR::Session& s) : ControlProtocol (s, string (X_("Ableton Push 2"))) , AbstractUI (name()) , handle (0) + , in_use (false) , _modifier_state (None) , splash_start (0) , _current_layout (0) @@ -140,7 +92,11 @@ Push2::Push2 (ARDOUR::Session& s) , _pressure_mode (AfterTouch) , selection_color (LED::Green) , contrast_color (LED::Green) + , in_range_select (false) { + /* we're going to need this */ + + libusb_init (NULL); build_maps (); build_color_map (); @@ -149,17 +105,21 @@ Push2::Push2 (ARDOUR::Session& s) /* master cannot be removed, so no need to connect to going-away signal */ 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); - /* catch current selection, if any */ - { - StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected())); - stripable_selection_change (sp); - } + /* allocate graphics layouts, even though we're not using them yet */ + + _canvas = new Push2Canvas (*this, 960, 160); + 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 */ ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this); @@ -167,82 +127,103 @@ Push2::Push2 (ARDOUR::Session& s) /* 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); - /* ports might already be there */ + /* Push 2 ports might already be there */ port_registration_handler (); } 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; + mix_layout = 0; delete scale_layout; + scale_layout = 0; + delete splash_layout; + splash_layout = 0; } void -Push2::port_registration_handler () +Push2::run_event_loop () { - if (!_async_in && !_async_out) { - /* ports not registered yet */ - return; - } + DEBUG_TRACE (DEBUG::Push2, "start event loop\n"); + BaseUI::run (); +} - if (_async_in->connected() && _async_out->connected()) { - /* don't waste cycles here */ - return; - } +void +Push2::stop_event_loop () +{ + 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 in; - vector out; +int +Push2::begin_using_device () +{ + DEBUG_TRACE (DEBUG::Push2, "begin using device\n"); - 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); + /* 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. + */ - 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()); - } + Glib::RefPtr 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::open () +Push2::stop_using_device () { - int err; + DEBUG_TRACE (DEBUG::Push2, "stop using device\n"); - if (handle) { - /* already open */ + if (!in_use) { + DEBUG_TRACE (DEBUG::Push2, "nothing to do, device not in use\n"); return 0; } - if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) { - return -1; - } + init_buttons (false); + strip_buttons_off (); - if ((err = libusb_claim_interface (handle, 0x00))) { - return -1; - } + vblank_connection.disconnect (); + session_connections.drop_connections (); - try { - _canvas = new Push2Canvas (*this, 960, 160); - mix_layout = new MixLayout (*this, *session); - scale_layout = new ScaleLayout (*this, *session); - track_mix_layout = new TrackMixLayout (*this, *session); - splash_layout = new SplashLayout (*this, *session); - } catch (...) { - error << _("Cannot construct Canvas for display") << endmsg; - libusb_release_interface (handle, 0x00); - libusb_close (handle); - return -1; - } + in_use = false; + return 0; +} + +int +Push2::ports_acquire () +{ + DEBUG_TRACE (DEBUG::Push2, "acquiring ports\n"); /* setup ports */ @@ -250,12 +231,13 @@ Push2::open () _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. + * really insist on that (and use JACK) */ _input_port = boost::dynamic_pointer_cast(_async_in).get(); @@ -284,26 +266,21 @@ Push2::open () connect_to_parser (); - return 0; -} + /* Connect input port to event loop */ -list > -Push2::bundles () -{ - list > b; + AsyncMIDIPort* asp; - if (_output_bundle) { - b.push_back (_output_bundle); - } + 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 b; + return 0; } -int -Push2::close () +void +Push2::ports_release () { - init_buttons (false); - strip_buttons_off (); + DEBUG_TRACE (DEBUG::Push2, "releasing ports\n"); /* wait for button data to be flushed */ AsyncMIDIPort* asp; @@ -317,29 +294,57 @@ Push2::close () _async_out.reset ((ARDOUR::Port*) 0); _input_port = 0; _output_port = 0; +} - periodic_connection.disconnect (); - session_connections.drop_connections (); +int +Push2::device_acquire () +{ + int err; - if (_current_layout) { - _canvas->root()->remove (_current_layout); - _current_layout = 0; + DEBUG_TRACE (DEBUG::Push2, "acquiring device\n"); + + if (handle) { + DEBUG_TRACE (DEBUG::Push2, "open() called with handle already set\n"); + /* already open */ + return 0; } - delete mix_layout; - mix_layout = 0; - delete scale_layout; - scale_layout = 0; - delete splash_layout; - splash_layout = 0; + if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) { + DEBUG_TRACE (DEBUG::Push2, "failed to open USB handle\n"); + return -1; + } + + 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) { libusb_release_interface (handle, 0x00); libusb_close (handle); handle = 0; } +} - return 0; +list > +Push2::bundles () +{ + list > b; + + if (_output_bundle) { + b.push_back (_output_bundle); + } + + return b; } void @@ -366,7 +371,7 @@ Push2::init_buttons (bool startup) */ ButtonID buttons[] = { Mute, Solo, Master, Up, Right, Left, Down, Note, Session, Mix, AddTrack, Delete, Undo, - Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session, DoubleLoop, + Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session, Quantize, Duplicate, Browse, PageRight, PageLeft, OctaveUp, OctaveDown, Layout, Scale }; @@ -413,16 +418,6 @@ Push2::init_buttons (bool startup) bool 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; } @@ -446,19 +441,10 @@ Push2::do_request (Push2Request * req) } else if (req->type == Quit) { - stop (); + stop_using_device (); } } -int -Push2::stop () -{ - BaseUI::quit (); - close (); - return 0; -} - - void Push2::splash () { @@ -501,49 +487,20 @@ Push2::set_active (bool yn) if (yn) { - /* start event loop */ - - BaseUI::run (); - - if (open ()) { - DEBUG_TRACE (DEBUG::Push2, "device open failed\n"); - close (); + if (device_acquire ()) { return -1; } - /* 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()); - - 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 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 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 (); + if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) { + begin_using_device (); + } else { + /* begin_using_device () will get called once we're connected */ + } } else { - - stop (); - + /* Control Protocol Manager never calls us with false, but + * insteads destroys us. + */ } ControlProtocol::set_active (yn); @@ -584,27 +541,23 @@ Push2::midi_input_handler (IOCondition ioc, MIDI::Port* port) 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(port); if (asp) { asp->clear (); } - //DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name())); - framepos_t now = AudioEngine::instance()->sample_time(); - port->parse (now); + DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name())); + if (in_use) { + framepos_t now = AudioEngine::instance()->sample_time(); + port->parse (now); + } } return true; } -bool -Push2::periodic () -{ - return true; -} - void Push2::connect_to_parser () { @@ -1213,10 +1166,51 @@ Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const 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 in; + 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()) { + 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 Push2::connection_handler (boost::weak_ptr, std::string name1, boost::weak_ptr, 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) { return false; } @@ -1237,11 +1231,14 @@ Push2::connection_handler (boost::weak_ptr, std::string name1, boo connection_state &= ~OutputConnected; } } 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 */ 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, @@ -1250,11 +1247,19 @@ Push2::connection_handler (boost::weak_ptr, std::string name1, boo */ g_usleep (100000); - DEBUG_TRACE (DEBUG::FaderPort, "device now connected for both input and output\n"); - connected (); + 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 */ @@ -1264,12 +1269,6 @@ Push2::connection_handler (boost::weak_ptr, std::string name1, boo return true; /* connection status changed */ } -void -Push2::connected () -{ - request_pressure_mode (); -} - boost::shared_ptr Push2::output_port() { @@ -1711,20 +1710,26 @@ Push2::get_color (ColorName name) void Push2::set_current_layout (Push2Layout* layout) { - if (_current_layout) { - _current_layout->hide (); - _canvas->root()->remove (_current_layout); - _previous_layout = _current_layout; - } + if (layout && layout == _current_layout) { + _current_layout->show (); + } else { - _current_layout = layout; + if (_current_layout) { + _current_layout->hide (); + _canvas->root()->remove (_current_layout); + _previous_layout = _current_layout; + } - if (_current_layout) { - _canvas->root()->add (_current_layout); - _current_layout->show (); - } + _current_layout = layout; - _canvas->request_redraw (); + if (_current_layout) { + _canvas->root()->add (_current_layout); + _current_layout->show (); + } + + + _canvas->request_redraw (); + } } void