Paul Davis
0d9efc1148
This new design will work even when threads that need to receive messages from RT threads are created *after* the RT threads. The existing design would fail because the RT thread(s) would never be known the later created threads, and so signals emitted by the RT thread and causing call_slot() in the receiver would end up being enqueued using a lock-protected list. The new design ensures that communication always uses a lock-free FIFO instead
458 lines
12 KiB
C++
458 lines
12 KiB
C++
/*
|
|
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 "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_mesg_callback (cwiid_wiimote_t *wiimote, int mesg_count, union cwiid_mesg mesg[], timespec *t);
|
|
|
|
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 ()
|
|
{
|
|
stop ();
|
|
}
|
|
|
|
bool
|
|
WiimoteControlProtocol::probe ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (yn) {
|
|
/* activate Wiimote control surface */
|
|
result = start ();
|
|
} else {
|
|
/* deactivate Wiimote control surface */
|
|
result = stop ();
|
|
}
|
|
|
|
ControlProtocol::set_active (yn);
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::set_active done\n");
|
|
|
|
return result;
|
|
}
|
|
|
|
XMLNode&
|
|
WiimoteControlProtocol::get_state ()
|
|
{
|
|
XMLNode& node (ControlProtocol::get_state());
|
|
node.add_property (X_("feedback"), "0");
|
|
return node;
|
|
}
|
|
|
|
int
|
|
WiimoteControlProtocol::set_state (const XMLNode&, int)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
WiimoteControlProtocol::do_request (WiimoteControlUIRequest* req)
|
|
{
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::do_request init\n");
|
|
|
|
if (req->type == CallSlot) {
|
|
call_slot (MISSING_INVALIDATOR, req->the_slot);
|
|
} else if (req->type == Quit) {
|
|
stop ();
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::do_request done\n");
|
|
}
|
|
|
|
int
|
|
WiimoteControlProtocol::start ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::start init\n");
|
|
|
|
// 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);
|
|
|
|
// start the Wiimote control UI; it will run in its own thread context
|
|
BaseUI::run ();
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::start done\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
WiimoteControlProtocol::stop ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::stop init\n");
|
|
|
|
// 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;
|
|
}
|
|
|
|
// stop the Wiimote control UI
|
|
BaseUI::quit ();
|
|
|
|
// no longer update the LEDs
|
|
session_connections.drop_connections ();
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::stop done\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
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_event_loops_about_thread_creation (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::start_wiimote_discovery ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::start_wiimote_discovery init\n");
|
|
|
|
// 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 ());
|
|
|
|
// grab a reference on the underlying idle source to keep it around
|
|
idle_source = source->gobj ();
|
|
g_source_ref (idle_source);
|
|
|
|
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;
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::stop_wiimote_discovery done\n");
|
|
}
|
|
|
|
bool
|
|
WiimoteControlProtocol::connect_idle ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::connect_idle init\n");
|
|
|
|
bool retry = false;
|
|
|
|
if (connect_wiimote ()) {
|
|
stop_wiimote_discovery ();
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::connect_idle done\n");
|
|
|
|
return retry;
|
|
}
|
|
|
|
bool
|
|
WiimoteControlProtocol::connect_wiimote ()
|
|
{
|
|
// abort the discovery and do nothing else if we already have a Wiimote
|
|
if (wiimote) {
|
|
return true;
|
|
}
|
|
|
|
bool success = false;
|
|
|
|
// 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
|
|
for (int i = 0; i < 5; ++i) {
|
|
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) {
|
|
// 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;
|
|
} else {
|
|
success = true;
|
|
// clear the last button state to start processing events cleanly
|
|
button_state = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// apply the LED state
|
|
cwiid_set_led (wiimote, state);
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::update_led_state done\n");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// skip non-button events
|
|
if (mesg[i].type != CWIID_MESG_BTN) {
|
|
continue;
|
|
}
|
|
|
|
// drop buttons from the event that were already pressed before
|
|
uint16_t b = mesg[i].btn_mesg.buttons & ~button_state;
|
|
|
|
// 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");
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
// 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 ();
|
|
}
|
|
|
|
// B + Home = add marker at playhead
|
|
if (b & CWIID_BTN_HOME) {
|
|
access_action ("Editor/add-location-from-playhead");
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
wiimote_control_protocol_mesg_callback (cwiid_wiimote_t *wiimote, int mesg_count, union cwiid_mesg mesg[], timespec *)
|
|
{
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::mesg_callback init\n");
|
|
|
|
WiimoteControlProtocol *protocol = reinterpret_cast<WiimoteControlProtocol*> (const_cast<void*>(cwiid_get_data (wiimote)));
|
|
|
|
if (protocol) {
|
|
protocol->wiimote_callback (mesg_count, mesg);
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::WiimoteControl, "WiimoteControlProtocol::mesg_callback done\n");
|
|
}
|