Merge branch 'master' into exportvis
[ardour.git] / libs / surfaces / wiimote / wiimote.cc
index 26f7e1810f58103c4d6461ff60669f0a96127f4a..68f2125dcb5a6824768b26cf868eccba0a03cf1c 100644 (file)
-#include "wiimote.h"
+/*
+    Copyright (C) 2009-2013 Paul Davis
+    Authors: Sampo Savolainen, Jannis Pohlmann
+
+    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 <iostream>
-#include <sigc++/bind.h>
 
-#include "pbd/xml++.h"
+#include "pbd/compose.h"
+#include "pbd/error.h"
+#include "ardour/debug.h"
 #include "ardour/session.h"
-
 #include "i18n.h"
 
+#include "wiimote.h"
 
 using namespace ARDOUR;
 using namespace PBD;
+using namespace std;
+
+#include "pbd/abstract_ui.cc" // instantiate template
 
-void wiimote_control_protocol_cwiid_callback(cwiid_wiimote_t *wiimote, int mesg_count, union cwiid_mesg mesg[], struct timespec *t);
+void wiimote_control_protocol_mesg_callback (cwiid_wiimote_t *wiimote, int mesg_count, union cwiid_mesg mesg[], timespec *t);
 
-uint16_t WiimoteControlProtocol::button_state = 0;
+WiimoteControlProtocol::WiimoteControlProtocol (Session& session)
+       : ControlProtocol (session, X_("Wiimote"))
+       , AbstractUI<WiimoteControlUIRequest> ("wiimote")
+       , wiimote (0)
+       , idle_source (0)
+       , button_state (0)
+       , callback_thread_registered (false)
+{
+}
 
-WiimoteControlProtocol::WiimoteControlProtocol ( Session & session) 
-       : ControlProtocol ( session, "Wiimote"),
-         main_thread_quit (false),
-         restart_discovery (false),
-         callback_thread_registered_for_ardour (false),
-         wiimote_handle (0)
+WiimoteControlProtocol::~WiimoteControlProtocol ()
 {
-       main_thread = Glib::Thread::create( sigc::mem_fun(*this, &WiimoteControlProtocol::wiimote_main), true);
+       stop ();
 }
 
-WiimoteControlProtocol::~WiimoteControlProtocol()
+bool
+WiimoteControlProtocol::probe ()
 {
-       main_thread_quit = true;
-       slot_cond.signal();
-       main_thread->join();
+       return true;
+}
 
-       if (wiimote_handle) {
-               cwiid_close(wiimote_handle);
+int
+WiimoteControlProtocol::set_active (bool yn)
+{
+       int result;
+
+       DEBUG_TRACE (DEBUG::WiimoteControl, string_compose ("WiimoteControlProtocol::set_active init with yn: '%1'\n", yn));
+
+       /* do nothing if the active state is not changing */
+       if (yn == _active) {
+               return 0;
        }
 
-       std::cerr << "Wiimote: closed" << std::endl;
+       if (yn) {
+               /* activate Wiimote control surface */
+               result = start ();
+       } else {
+               /* deactivate Wiimote control surface */
+               result = stop ();
+       }
+
+       /* remember new active state */
+       _active = yn;
+
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::set_active done\n");
+
+       return result;
 }
 
-bool 
-WiimoteControlProtocol::probe()
+XMLNode&
+WiimoteControlProtocol::get_state ()
 {
-       return true;
+       XMLNode *node = new XMLNode ("Protocol");
+       node->add_property (X_("name"), ARDOUR::ControlProtocol::_name);
+       node->add_property (X_("feedback"), "0");
+       return *node;
+}
+
+int
+WiimoteControlProtocol::set_state (const XMLNode&, int)
+{
+       return 0;
 }
 
 void
-WiimoteControlProtocol::wiimote_callback(cwiid_wiimote_t *wiimote, int mesg_count, union cwiid_mesg mesg[], struct timespec *t)
+WiimoteControlProtocol::do_request (WiimoteControlUIRequest* req)
 {
-       int i;
-       uint16_t b;
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::do_request init\n");
 
-       if (!callback_thread_registered_for_ardour) {
-               register_thread("Wiimote Control Protocol");
-               callback_thread_registered_for_ardour = true;
+       if (req->type == CallSlot) {
+               call_slot (MISSING_INVALIDATOR, req->the_slot);
+       } else if (req->type == Quit) {
+               stop ();
        }
 
-        for (i=0; i < mesg_count; i++)
-       {
-               if (mesg[i].type == CWIID_MESG_ERROR) { 
-                       std::cerr << "Wiimote: disconnect" << std::endl;
-                       restart_discovery = true;
-                       slot_cond.signal();
-                       return;
-               }
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::do_request done\n");
+}
 
-               if (mesg[i].type != CWIID_MESG_BTN) continue;
+int
+WiimoteControlProtocol::start ()
+{
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::start init\n");
 
-               // what buttons are pressed down which weren't pressed down last time
-               b = (mesg[i].btn_mesg.buttons ^ button_state) & mesg[i].btn_mesg.buttons;
+       // update LEDs whenever the transport or recording state changes
+       session->TransportStateChange.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&WiimoteControlProtocol::update_led_state, this), this);
+       session->RecordStateChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&WiimoteControlProtocol::update_led_state, this), this);
 
-               button_state = mesg[i].btn_mesg.buttons;
-       
-               // if B is pressed down
-               if (button_state & CWIID_BTN_B) {
-                       if (b & CWIID_BTN_A) { // B is down and A is pressed
-                               access_action("Transport/ToggleRollForgetCapture");
-                       }
+       // start the Wiimote control UI; it will run in its own thread context
+       BaseUI::run ();
 
-                       if (b & CWIID_BTN_LEFT) {
-                               access_action("Editor/playhead-to-previous-region-boundary");
-                       }
-                       if (b & CWIID_BTN_RIGHT) {
-                               access_action("Editor/playhead-to-next-region-boundary");
-                       }
-                       if (b & CWIID_BTN_UP) {
-                               next_marker();
-                       }
-                       if (b & CWIID_BTN_DOWN) {
-                               prev_marker();
-                       }
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::start done\n");
 
-                       if (b & CWIID_BTN_HOME) {
-                               access_action("Editor/add-location-from-playhead");
-                       }
+       return 0;
+}
 
-                       if (b & CWIID_BTN_MINUS) {      
-                               access_action("Transport/GotoStart");
-                       }
-                       
-                       if (b & CWIID_BTN_PLUS) {
-                               access_action("Transport/GotoEnd");
-                       }
+int
+WiimoteControlProtocol::stop ()
+{
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::stop init\n");
 
-                       continue;
-               }
+       // stop wiimote discovery, just in case
+       stop_wiimote_discovery ();
 
+       // close and reset the wiimote handle
+       if (wiimote) {
+               cwiid_close (wiimote);
+               wiimote = 0;
+               callback_thread_registered = false;
+       }
 
-               if (b & CWIID_BTN_A) {
-                       access_action("Transport/ToggleRoll");
-               }
+       // stop the Wiimote control UI
+       BaseUI::quit ();
 
-               if (b & CWIID_BTN_1) { // 1
-                       access_action("Editor/track-record-enable-toggle");
-               }
-               if (b & CWIID_BTN_2) { // 2
-                       rec_enable_toggle();
-               }
+       // no longer update the LEDs
+       session_connections.drop_connections ();
 
-               // d-pad
-               if (b & CWIID_BTN_LEFT) { // left
-                       access_action("Editor/nudge-playhead-backward");
-               }
-               if (b & CWIID_BTN_RIGHT) { // right
-                       access_action("Editor/nudge-playhead-forward");
-               }
-               if (b & CWIID_BTN_DOWN) { // down
-                       access_action("Editor/select-next-route");
-               }
-               if (b & CWIID_BTN_UP) { // up
-                       access_action("Editor/select-prev-route");
-               }
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::stop done\n");
 
+       return 0;
+}
 
-               if (b & CWIID_BTN_PLUS) { // +
-                       access_action("Editor/temporal-zoom-in");
-               }
-               if (b & CWIID_BTN_MINUS) { // -
-                       access_action("Editor/temporal-zoom-out");
-               }
-               if (b & CWIID_BTN_HOME) { // "home"
-                       // no op, yet. any suggestions?
-                       access_action("Editor/playhead-to-edit");
-               }
+void
+WiimoteControlProtocol::thread_init ()
+{
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::thread_init init\n");
 
-       }
+       pthread_set_name (X_("wiimote"));
+
+       // allow to make requests to the GUI and RT thread(s)
+       PBD::notify_gui_about_thread_creation (X_("gui"), pthread_self (), X_("wiimote"), 2048);
+       BasicUI::register_thread ("wiimote");
+
+       // connect a Wiimote
+       start_wiimote_discovery ();
+
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::thread_init done\n");
 }
 
 void
-WiimoteControlProtocol::update_led_state()
+WiimoteControlProtocol::start_wiimote_discovery ()
 {
-       ENSURE_WIIMOTE_THREAD(sigc::mem_fun(*this, &WiimoteControlProtocol::update_led_state));
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::start_wiimote_discovery init\n");
 
-       uint8_t state = 0;
+       // connect to the Wiimote using an idle source
+       Glib::RefPtr<Glib::IdleSource> source = Glib::IdleSource::create ();
+       source->connect (sigc::mem_fun (*this, &WiimoteControlProtocol::connect_idle));
+       source->attach (_main_loop->get_context ());
 
-       if (session->transport_rolling()) {
-               state |= CWIID_LED1_ON;
-       }
+       // grab a reference on the underlying idle source to keep it around
+       idle_source = source->gobj ();
+       g_source_ref (idle_source);
 
-       if (session->actively_recording()) {
-               state |= CWIID_LED4_ON;
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::start_wiimote_discovery done\n");
+}
+
+void
+WiimoteControlProtocol::stop_wiimote_discovery ()
+{
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::stop_wiimote_discovery init\n");
+
+       if (idle_source) {
+               g_source_unref (idle_source);
+               idle_source = 0;
        }
 
-       cwiid_set_led(wiimote_handle, state);
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::stop_wiimote_discovery done\n");
 }
 
-void
-WiimoteControlProtocol::_wiimote_main ()
+bool
+WiimoteControlProtocol::connect_idle ()
 {
-       bdaddr_t bdaddr;
-       unsigned char rpt_mode = 0;
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::connect_idle init\n");
 
-       register_thread ("Wiimote");
+       bool retry = true;
 
-wiimote_discovery:
+       if (connect_wiimote ()) {
+               stop_wiimote_discovery ();
+               retry = false;
+       }
 
-       std::cerr << "Wiimote: discovering, press 1+2" << std::endl;
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::connect_idle done\n");
 
-       while (!wiimote_handle && !main_thread_quit) {
-               bdaddr = (bdaddr_t) {{0, 0, 0, 0, 0, 0}};
-               callback_thread_registered_for_ardour = false;
-               wiimote_handle = cwiid_open(&bdaddr, 0);
+       return retry;
+}
 
-               if (!wiimote_handle && !main_thread_quit) {
-                       sleep(1); 
-                       // We don't know whether the issue was a timeout or a configuration 
-                       // issue
-               }
+bool
+WiimoteControlProtocol::connect_wiimote ()
+{
+       // abort the discovery and do nothing else if we already have a Wiimote
+       if (wiimote) {
+               return true;
        }
 
-       if (main_thread_quit) {
-               // The corner case where the wiimote is bound at the same time as
-               // the control protocol is destroyed
-               if (wiimote_handle) {
-                       cwiid_close(wiimote_handle);
+       bool success = true;
+
+       // if we don't have a Wiimote yet, try to discover it; if that
+       // fails, wait for a short period of time and try again
+       if (!wiimote) {
+               cerr << "Wiimote: Not discovered yet, press 1+2 to connect" << endl;
+
+               bdaddr_t bdaddr = {{ 0, 0, 0, 0, 0, 0 }};
+               wiimote = cwiid_open (&bdaddr, 0);
+               callback_thread_registered = false;
+               if (!wiimote) {
+                       success = false;
+               } else {
+                       // a Wiimote was discovered
+                       cerr << "Wiimote: Connected successfully" << endl;
+
+                       // attach the WiimoteControlProtocol object to the Wiimote handle
+                       if (cwiid_set_data (wiimote, this)) {
+                               cerr << "Wiimote: Failed to attach control protocol" << endl;
+                               success = false;
+                       }
+
+                       // clear the last button state to start processing events cleanly
+                       button_state = 0;
                }
-               wiimote_handle = 0;
+       }
 
-               std::cerr << "Wiimote Control Protocol stopped before connected to a wiimote" << std::endl;
-               return;
+       // enable message based communication with the Wiimote
+       if (success && cwiid_enable (wiimote, CWIID_FLAG_MESG_IFC)) {
+               cerr << "Wiimote: Failed to enable message based communication" << endl;
+               success = false;
        }
 
-       std::cerr << "Wiimote: connected" << std::endl;
-       WiimoteControlProtocol::button_state = 0;
+       // enable button events to be received from the Wiimote
+       if (success && cwiid_command (wiimote, CWIID_CMD_RPT_MODE, CWIID_RPT_BTN)) {
+               cerr << "Wiimote: Failed to enable button events" << endl;
+               success = false;
+       }
 
-       if (cwiid_enable(wiimote_handle, CWIID_FLAG_REPEAT_BTN)) {
-               std::cerr << "cwiid_enable(), error" << std::endl;
-               cwiid_close(wiimote_handle);
-               wiimote_handle = 0;
-               return;
+       // receive an event for every single button pressed, not just when
+       // a different button was pressed than before
+       if (success && cwiid_enable (wiimote, CWIID_FLAG_REPEAT_BTN)) {
+               cerr << "Wiimote: Failed to enable repeated button events" << endl;
+               success = false;
        }
-       if (cwiid_set_mesg_callback(wiimote_handle, wiimote_control_protocol_cwiid_callback)) {
-               std::cerr << "cwiid_set_mesg_callback(), couldn't connect callback" << std::endl;
-               cwiid_close(wiimote_handle);
-               wiimote_handle = 0;
-               return;
-       } 
-       if (cwiid_command(wiimote_handle, CWIID_CMD_RPT_MODE, CWIID_RPT_BTN)) {
-               std::cerr << "cwiid_command(), RPT_MODE error" << std::endl;
-               cwiid_close(wiimote_handle);
-               wiimote_handle = 0;
+
+       // be notified of new input events
+       if (success && cwiid_set_mesg_callback (wiimote, wiimote_control_protocol_mesg_callback)) {
+       }
+
+       // reset Wiimote handle if the configuration failed
+       if (!success && wiimote) {
+               cwiid_close (wiimote);
+               wiimote = 0;
+               callback_thread_registered = false;
+       }
+
+       return success;
+}
+
+void
+WiimoteControlProtocol::update_led_state ()
+{
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::update_led_state init\n");
+
+       uint8_t state = 0;
+
+       // do nothing if we do not have a Wiimote
+       if (!wiimote) {
+               DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::update_led_state no wiimote connected\n");
                return;
        }
 
-       rpt_mode |= CWIID_RPT_BTN;
-       cwiid_enable(wiimote_handle, CWIID_FLAG_MESG_IFC);
-       cwiid_set_rpt_mode(wiimote_handle, rpt_mode);
+       // enable LED1 if Ardour is playing
+       if (session->transport_rolling ()) {
+               DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::update_led_state playing, activate LED1\n");
+               state |= CWIID_LED1_ON;
+       }
 
-       transport_state_conn = session->TransportStateChange.connect(sigc::mem_fun(*this, &WiimoteControlProtocol::update_led_state));
-       record_state_conn = session->RecordStateChanged.connect(sigc::mem_fun(*this, &WiimoteControlProtocol::update_led_state));
+       // enable LED4 if Ardour is recording
+       if (session->actively_recording ()) {
+               DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::update_led_state recording, activate LED4\n");
+               state |= CWIID_LED4_ON;
+       }
 
-       std::cerr << "Wiimote: initialization done, waiting for callbacks / quit" << std::endl;
+       // apply the LED state
+       cwiid_set_led (wiimote, state);
+
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::update_led_state done\n");
+}
 
-       while (!main_thread_quit) {
-               slot_mutex.lock();
-               while (slot_list.empty() && !main_thread_quit && !restart_discovery)
-                       slot_cond.wait(slot_mutex);
+void
+WiimoteControlProtocol::wiimote_callback (int mesg_count, union cwiid_mesg mesg[])
+{
+       // register the cwiid callback thread if that hasn't happened yet
+       if (!callback_thread_registered) {
+               BasicUI::register_thread ("wiimote callback");
+               callback_thread_registered = true;
+       }
 
-               if (main_thread_quit) {
-                       slot_mutex.unlock();
-                       break;
+       for (int i = 0; i < mesg_count; i++) {
+               // restart Wiimote discovery when receiving errors
+               if (mesg[i].type == CWIID_MESG_ERROR) {
+                       cerr << "Wiimote: disconnected" << endl;
+                       cwiid_close (wiimote);
+                       wiimote = 0;
+                       callback_thread_registered = false;
+                       start_wiimote_discovery ();
+                       return;
                }
 
-               if (restart_discovery) {
-                       std::cerr << "Wiimote: closing wiimote and restarting discovery" << std::endl;
-                       if (wiimote_handle) {
-                               cwiid_close(wiimote_handle);
-                               wiimote_handle = 0;
-                       }
-                       slot_mutex.unlock();
-                       restart_discovery = false;
-                       goto wiimote_discovery;
+               // skip non-button events
+               if (mesg[i].type != CWIID_MESG_BTN) {
+                       continue;
                }
 
-               sigc::slot<void> call_me = *slot_list.begin();
-               slot_list.pop_front();
-               slot_mutex.unlock();
+               // drop buttons from the event that were already pressed before
+               uint16_t b = mesg[i].btn_mesg.buttons & ~button_state;
 
-               call_me();
-       }
+               // remember new button state
+               button_state = mesg[i].btn_mesg.buttons;
 
+               if (button_state & CWIID_BTN_B) {
+                       // B + A = abort recording and jump back
+                       if (b & CWIID_BTN_A) {
+                               access_action ("Transport/ToggleRollForgetCapture");
+                       }
 
-       std::cerr << "Wiimote: main thread stopped" << std::endl;
-       return 0;
-}
+                       // B + left = move playhead to previous region boundary
+                       if (b & CWIID_BTN_LEFT) {
+                               access_action ("Editor/playhead-to-previous-region-boundary");
+                       }
 
+                       // B + right = move playhead to next region boundary
+                       if (b & CWIID_BTN_RIGHT) {
+                               access_action ("Editor/playhead-to-next-region-boundary");
+                       }
 
-int
-WiimoteControlProtocol::set_active (bool yn)
-{
-       // Let's not care about this just yet
-       return 0;
+                       // B + up = move playhead to next marker
+                       if (b & CWIID_BTN_UP) {
+                               next_marker ();
+                       }
 
-}
+                       // B + down = move playhead to prev marker
+                       if (b & CWIID_BTN_DOWN) {
+                               prev_marker ();
+                       }
 
-XMLNode&
-WiimoteControlProtocol::get_state()
-{
-       XMLNode *node = new XMLNode ("Protocol");
-        node->add_property (X_("name"), _name);
-        node->add_property (X_("feedback"), "0");
+                       // B + Home = add marker at playhead
+                       if (b & CWIID_BTN_HOME) {
+                               access_action ("Editor/add-location-from-playhead");
+                       }
 
-       return *node;
+                       // B + minus = move playhead to the start
+                       if (b & CWIID_BTN_MINUS) {
+                               access_action ("Transport/GotoStart");
+                       }
+
+                       // B + plus = move playhead to the end
+                       if (b & CWIID_BTN_PLUS) {
+                               access_action ("Transport/GotoEnd");
+                       }
+               } else {
+                       // A = toggle playback
+                       if (b & CWIID_BTN_A) {
+                               access_action ("Transport/ToggleRoll");
+                       }
+
+                       // 1 = toggle recording on the current track
+                       if (b & CWIID_BTN_1) {
+                               access_action ("Editor/track-record-enable-toggle");
+                       }
+
+                       // 2 = enable recording in general
+                       if (b & CWIID_BTN_2) {
+                               rec_enable_toggle ();
+                       }
+
+                       // left = move playhead back a bit
+                       if (b & CWIID_BTN_LEFT) {
+                               access_action ("Editor/nudge-playhead-backward");
+                       }
+
+                       // right = move playhead forward a bit
+                       if (b & CWIID_BTN_RIGHT) {
+                               access_action ("Editor/nudge-playhead-forward");
+                       }
+
+                       // up = select previous track
+                       if (b & CWIID_BTN_UP) {
+                               access_action ("Editor/select-prev-route");
+                       }
+
+                       // down = select next track
+                       if (b & CWIID_BTN_DOWN) {
+                               access_action ("Editor/select-next-route");
+                       }
+
+                       // + = zoom in
+                       if (b & CWIID_BTN_PLUS) {
+                               access_action ("Editor/temporal-zoom-in");
+                       }
+
+                       // - = zoom out
+                       if (b & CWIID_BTN_MINUS) {
+                               access_action ("Editor/temporal-zoom-out");
+                       }
+
+                       // home = no-op
+                       if (b & CWIID_BTN_HOME) {
+                               access_action ("Editor/playhead-to-edit");
+                       }
+               }
+       }
 }
 
-int
-WiimoteControlProtocol::set_state(const XMLNode& node)
+void
+wiimote_control_protocol_mesg_callback (cwiid_wiimote_t *wiimote, int mesg_count, union cwiid_mesg mesg[], timespec *)
 {
-       return 0;
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::mesg_callback init\n");
+
+       WiimoteControlProtocol *protocol = (WiimoteControlProtocol *)cwiid_get_data (wiimote);
+
+       if (protocol) {
+               protocol->wiimote_callback (mesg_count, mesg);
+       }
+
+       DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::mesg_callback done\n");
 }