13
0
livetrax/libs/surfaces/contourdesign/contourdesign.cc

694 lines
17 KiB
C++

/*
* Copyright (C) 2019 Johannes Mueller <github@johannes-mueller.org>
* Copyright (C) 2019 Robin Gareus <robin@gareus.org>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <iostream>
#include <libusb.h>
#include <glibmm.h>
#include "pbd/compose.h"
#include "pbd/error.h"
#include "ardour/debug.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "pbd/i18n.h"
#include "contourdesign.h"
using namespace ARDOUR;
using namespace PBD;
using namespace Glib;
using namespace std;
using namespace ArdourSurface;
#include "pbd/abstract_ui.cc" // instantiate template
static const uint16_t ContourDesign = 0x0b33;
static const uint16_t ShuttlePRO_id = 0x0010;
static const uint16_t ShuttlePRO_v2_id = 0x0030;
static const uint16_t ShuttleXpress_id = 0x0020;
static void LIBUSB_CALL event_callback (struct libusb_transfer* transfer);
ContourDesignControlProtocol::ContourDesignControlProtocol (Session& session)
: ControlProtocol (session, X_("ContourDesign"))
, AbstractUI<ContourDesignControlUIRequest> ("contourdesign")
, _io_source (0)
, _dev_handle (0)
, _usb_transfer (0)
, _supposed_to_quit (false)
, _device_type (None)
, _shuttle_was_zero (true)
, _was_rolling_before_shuttle (false)
, _test_mode (false)
, _keep_rolling (true)
, _shuttle_speeds ()
, _jog_distance ()
, _gui (0)
{
libusb_init (0);
// libusb_set_debug(0, LIBUSB_LOG_LEVEL_WARNING);
_shuttle_speeds.push_back (0.50);
_shuttle_speeds.push_back (0.75);
_shuttle_speeds.push_back (1.0);
_shuttle_speeds.push_back (1.5);
_shuttle_speeds.push_back (2.0);
_shuttle_speeds.push_back (5.0);
_shuttle_speeds.push_back (10.0);
setup_default_button_actions ();
BaseUI::run();
}
ContourDesignControlProtocol::~ContourDesignControlProtocol ()
{
stop ();
libusb_exit (0);
BaseUI::quit();
tear_down_gui ();
}
bool
ContourDesignControlProtocol::available ()
{
bool rv = LIBUSB_SUCCESS == libusb_init (0);
if (rv) {
libusb_exit (0);
}
return rv;
}
bool
ContourDesignControlProtocol::match_usb (uint16_t vendor, uint16_t p)
{
if (vendor != ContourDesign) {
return false;
}
return (p == ShuttlePRO_id || p == ShuttlePRO_v2_id || p == ShuttleXpress_id);
}
int
ContourDesignControlProtocol::set_active (bool yn)
{
DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose ("set_active() init with yn: '%1'\n", yn));
if (yn == active()) {
return 0;
}
if (yn) {
start ();
} else {
stop ();
}
ControlProtocol::set_active (yn);
return _error;
}
XMLNode&
ContourDesignControlProtocol::get_state () const
{
XMLNode& node (ControlProtocol::get_state());
node.set_property (X_("keep-rolling"), _keep_rolling);
ostringstream os;
vector<double>::const_iterator it = _shuttle_speeds.begin ();
os << *(it++);
for (; it != _shuttle_speeds.end (); ++it) {
os << ' ' << *it;
}
string s = os.str ();
node.set_property (X_("shuttle-speeds"), s);
node.set_property (X_("jog-distance"), _jog_distance.value);
switch (_jog_distance.unit) {
case SECONDS: s = X_("seconds"); break;
case BARS: s = X_("bars"); break;
case BEATS:
default: s = X_("beats");
}
node.set_property (X_("jog-unit"), s);
for (unsigned int i=0; i<_button_actions.size(); ++i) {
XMLNode* child = new XMLNode (string_compose (X_("button-%1"), i+1));
node.add_child_nocopy (_button_actions[i]->get_state (*child));
}
return node;
}
int
ContourDesignControlProtocol::set_state (const XMLNode& node, int version)
{
if (ControlProtocol::set_state (node, version)) {
return -1;
}
node.get_property (X_("keep-rolling"), _keep_rolling);
string s;
node.get_property (X_("shuttle-speeds"), s);
istringstream is (s);
for (vector<double>::iterator it = _shuttle_speeds.begin (); it != _shuttle_speeds.end (); ++it) {
is >> *it;
}
node.get_property (X_("jog-distance"), _jog_distance.value);
node.get_property (X_("jog-unit"), s);
if (s == "seconds") {
_jog_distance.unit = SECONDS;
} else if (s == "bars") {
_jog_distance.unit = BARS;
} else {
_jog_distance.unit = BEATS;
}
XMLNode* child;
for (unsigned int i=0; i<_button_actions.size(); ++i) {
if ((child = node.child (string_compose(X_("button-%1"), i+1).c_str())) == 0) {
continue;
}
string type;
child->get_property (X_("type"), type);
if (type == X_("action")) {
string path ("");
child->get_property (X_("path"), path);
std::shared_ptr<ButtonBase> b (new ButtonAction (path, *this));
_button_actions[i] = b;
} else {
double value;
string s;
if (!child->get_property(X_("value"), value)) {
continue;
}
if (!child->get_property(X_("unit"), s)) {
continue;
}
JumpUnit unit;
if (s == X_("seconds")) {
unit = SECONDS;
} else if (s == X_("bars")) {
unit = BARS;
} else {
unit = BEATS;
}
std::shared_ptr<ButtonBase> b (new ButtonJump (JumpDistance (value, unit), *this));
}
}
return 0;
}
void
ContourDesignControlProtocol::do_request (ContourDesignControlUIRequest* req)
{
if (req->type == CallSlot) {
DEBUG_TRACE (DEBUG::ContourDesignControl, "do_request type CallSlot\n");
call_slot (MISSING_INVALIDATOR, req->the_slot);
} else if (req->type == Quit) {
DEBUG_TRACE (DEBUG::ContourDesignControl, "do_request type Quit\n");
stop ();
}
}
void
ContourDesignControlProtocol::thread_init ()
{
DEBUG_TRACE (DEBUG::ContourDesignControl, "thread_init()\n");
PBD::notify_event_loops_about_thread_creation (pthread_self (), X_("contourdesign"), 2048);
ARDOUR::SessionEvent::create_per_thread_pool (X_("contourdesign"), 128);
set_thread_priority ();
}
bool
ContourDesignControlProtocol::wait_for_event ()
{
DEBUG_TRACE (DEBUG::ContourDesignControl, "wait_for_event\n");
if (!_supposed_to_quit) {
libusb_handle_events (0);
}
return true;
}
int
get_usb_device (uint16_t vendor_id, uint16_t product_id, libusb_device** device)
{
struct libusb_device **devs;
struct libusb_device *dev;
size_t i = 0;
int r = LIBUSB_ERROR_NO_DEVICE;
*device = 0;
if (libusb_get_device_list (0, &devs) < 0) {
return LIBUSB_ERROR_NO_DEVICE;
}
while ((dev = devs[i++])) {
struct libusb_device_descriptor desc;
r = libusb_get_device_descriptor (dev, &desc);
if (r != LIBUSB_SUCCESS) {
goto out;
}
if (desc.idVendor == vendor_id && desc.idProduct == product_id) {
*device = dev;
break;
}
}
out:
libusb_free_device_list(devs, 1);
if (!dev && r == LIBUSB_SUCCESS) {
return LIBUSB_ERROR_NO_DEVICE;
}
return r;
}
int
ContourDesignControlProtocol::acquire_device ()
{
DEBUG_TRACE (DEBUG::ContourDesignControl, "acquire_device()\n");
int err;
if (_dev_handle) {
DEBUG_TRACE (DEBUG::ContourDesignControl, "already have a device handle\n");
return LIBUSB_SUCCESS;
}
libusb_device* dev;
if ((err = get_usb_device (ContourDesign, ShuttleXpress_id, &dev)) == LIBUSB_SUCCESS) {
_device_type = ShuttleXpress;
}
else if ((err = get_usb_device (ContourDesign, ShuttlePRO_id, &dev)) == LIBUSB_SUCCESS) {
_device_type = ShuttlePRO;
}
else if ((err = get_usb_device (ContourDesign, ShuttlePRO_v2_id, &dev)) == LIBUSB_SUCCESS) {
_device_type = ShuttlePRO_v2;
}
else {
_device_type = None;
return err;
}
err = libusb_open (dev, &_dev_handle);
if (err != LIBUSB_SUCCESS) {
return err;
}
libusb_set_auto_detach_kernel_driver (_dev_handle, true);
if ((err = libusb_claim_interface (_dev_handle, 0x00)) != LIBUSB_SUCCESS) {
DEBUG_TRACE (DEBUG::ContourDesignControl, "failed to claim USB device\n");
goto usb_close;
}
_usb_transfer = libusb_alloc_transfer (0);
if (!_usb_transfer) {
DEBUG_TRACE (DEBUG::ContourDesignControl, "failed to alloc usb transfer\n");
err = LIBUSB_ERROR_NO_MEM;
goto usb_close;
}
libusb_fill_interrupt_transfer (_usb_transfer, _dev_handle, 1 | LIBUSB_ENDPOINT_IN, _buf, sizeof(_buf),
event_callback, this, 0);
DEBUG_TRACE (DEBUG::ContourDesignControl, "callback installed\n");
if ((err = libusb_submit_transfer (_usb_transfer)) != LIBUSB_SUCCESS) {
DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose ("failed to submit tansfer: %1\n", err));
goto free_transfer;
}
return LIBUSB_SUCCESS;
free_transfer:
libusb_free_transfer (_usb_transfer);
usb_close:
libusb_close (_dev_handle);
_dev_handle = 0;
return err;
}
void
ContourDesignControlProtocol::release_device ()
{
if (!_dev_handle) {
return;
}
libusb_close (_dev_handle);
libusb_free_transfer (_usb_transfer);
libusb_release_interface (_dev_handle, 0);
_usb_transfer = 0;
_dev_handle = 0;
}
void
ContourDesignControlProtocol::start ()
{
DEBUG_TRACE (DEBUG::ContourDesignControl, "start()\n");
_supposed_to_quit = false;
_error = acquire_device();
if (_error != LIBUSB_SUCCESS) {
return;
}
if (!_dev_handle) { // can this actually happen?
_error = -1;
return;
}
_state.shuttle = 0;
_state.jog = 0;
_state.buttons = 0;
Glib::RefPtr<Glib::IdleSource> source = Glib::IdleSource::create ();
source->connect (sigc::mem_fun (*this, &ContourDesignControlProtocol::wait_for_event));
source->attach (_main_loop->get_context ());
_io_source = source->gobj ();
g_source_ref (_io_source);
}
void
ContourDesignControlProtocol::stop ()
{
DEBUG_TRACE (DEBUG::ContourDesignControl, "stop()\n");
_supposed_to_quit = true;
if (_io_source) {
g_source_destroy (_io_source);
g_source_unref (_io_source);
_io_source = 0;
}
if (_dev_handle) {
release_device ();
}
}
void
ContourDesignControlProtocol::handle_event () {
if (_usb_transfer->status == LIBUSB_TRANSFER_TIMED_OUT) {
goto resubmit;
}
if (_usb_transfer->status != LIBUSB_TRANSFER_COMPLETED) {
DEBUG_TRACE (DEBUG::ContourDesignControl, string_compose("libusb_transfer not completed: %1\n", _usb_transfer->status));
_error = LIBUSB_ERROR_NO_DEVICE;
return;
}
State new_state;
new_state.shuttle = _buf[0];
new_state.jog = _buf[1];
new_state.buttons = (_buf[4] << 8) + _buf[3];
// cout << "event " << (int)new_state.shuttle << " " << (int)new_state.jog << " " << (int)new_state.buttons << endl;;
for (uint8_t btn=0; btn<16; btn++) {
if ( (new_state.buttons & (1<<btn)) && !(_state.buttons & (1<<btn)) ) {
handle_button_press (btn);
} else if ( !(new_state.buttons & (1<<btn)) && (_state.buttons & (1<<btn)) ) {
handle_button_release (btn);
}
}
if (new_state.jog == 255 && _state.jog == 0) {
jog_event_backward ();
} else if (new_state.jog == 0 && _state.jog == 255) {
jog_event_forward();
} else if (new_state.jog < _state.jog) {
jog_event_backward();
} else if (new_state.jog > _state.jog) {
jog_event_forward();
}
if (new_state.shuttle != _state.shuttle) {
shuttle_event(new_state.shuttle);
}
_state = new_state;
resubmit:
if (libusb_submit_transfer (_usb_transfer)) {
DEBUG_TRACE (DEBUG::ContourDesignControl, "failed to resubmit usb transfer after callback\n");
stop ();
}
}
std::shared_ptr<ButtonBase>
ContourDesignControlProtocol::make_button_action (string action_string)
{
return std::shared_ptr<ButtonBase> (new ButtonAction (action_string, *this));
}
/* The buttons have the following layout
*
* 00 01 02 03
* 04 05 06 07 08
*
* 13 Jog 14
*
* 09 10
* 11 12
*/
void
ContourDesignControlProtocol::setup_default_button_actions ()
{
_button_actions.push_back (make_button_action ("MIDI/panic"));
_button_actions.push_back (make_button_action ("Editor/remove-last-capture"));
_button_actions.push_back (make_button_action ("Editor/undo"));
_button_actions.push_back (make_button_action ("Editor/redo"));
_button_actions.push_back (make_button_action ("Common/jump-backward-to-mark"));
_button_actions.push_back (make_button_action ("Transport/Record"));
_button_actions.push_back (make_button_action ("Transport/Stop"));
_button_actions.push_back (make_button_action ("Transport/Roll"));
_button_actions.push_back (make_button_action ("Common/jump-forward-to-mark"));
_button_actions.push_back (std::shared_ptr<ButtonBase> (new ButtonJump (JumpDistance (-4.0, BARS), *this)));
_button_actions.push_back (std::shared_ptr<ButtonBase> (new ButtonJump (JumpDistance (+4.0, BARS), *this)));
_button_actions.push_back (make_button_action (""));
_button_actions.push_back (make_button_action ("Common/add-location-from-playhead"));
_button_actions.push_back (make_button_action ("Transport/GotoStart"));
_button_actions.push_back (make_button_action ("Transport/GotoEnd"));
}
const std::shared_ptr<ButtonBase>
ContourDesignControlProtocol::get_button_action (unsigned int index) const
{
if (index >= _button_actions.size()) {
return std::shared_ptr<ButtonBase>();
}
return _button_actions[index];
}
void
ContourDesignControlProtocol::set_button_action (unsigned int index, const std::shared_ptr<ButtonBase> btn_act)
{
if (index >= _button_actions.size()) {
return;
}
_button_actions[index] = btn_act;
}
void
ContourDesignControlProtocol::handle_button_press (unsigned short btn)
{
if (_test_mode) {
ButtonPress (btn); /* emit signal */
return;
}
if (btn >= _button_actions.size ()) {
DEBUG_TRACE (DEBUG::ContourDesignControl,
string_compose ("ContourDesign button number out of bounds %1, max is %2\n",
btn, _button_actions.size ()));
return;
}
_button_actions[btn]->execute ();
}
void
ContourDesignControlProtocol::handle_button_release (unsigned short btn)
{
if (_test_mode) {
ButtonRelease (btn); /* emit signal */
}
}
void
ContourDesignControlProtocol::prev_marker_keep_rolling ()
{
timepos_t pos = session->locations()->first_mark_before (timepos_t (session->transport_sample()));
if (pos.is_positive() || pos.is_zero()) {
session->request_locate (pos.samples());
} else {
session->goto_start ();
}
}
void
ContourDesignControlProtocol::next_marker_keep_rolling ()
{
timepos_t pos = session->locations()->first_mark_after (timepos_t (session->transport_sample()));
if (pos.is_positive() || pos.is_zero ()) {
session->request_locate (pos.samples());
} else {
session->goto_end();
}
}
void
ContourDesignControlProtocol::jog_event_backward ()
{
DEBUG_TRACE (DEBUG::ContourDesignControl, "jog event backward\n");
jump_backward (_jog_distance);
}
void
ContourDesignControlProtocol::jog_event_forward ()
{
DEBUG_TRACE (DEBUG::ContourDesignControl, "jog event forward\n");
jump_forward (_jog_distance);
}
void
ContourDesignControlProtocol::jump_forward (JumpDistance dist)
{
LocateTransportDisposition kr = _keep_rolling ? RollIfAppropriate : MustStop;
switch (dist.unit) {
case SECONDS: jump_by_seconds (dist.value, kr); break;
case BEATS: jump_by_beats (dist.value, kr); break;
case BARS: jump_by_bars (dist.value, kr); break;
default: break;
}
}
void ContourDesignControlProtocol::jump_backward (JumpDistance dist)
{
JumpDistance bw = dist;
bw.value = -bw.value;
jump_forward(bw);
}
void
ContourDesignControlProtocol::set_shuttle_speed (unsigned int index, double speed)
{
if (index >= _shuttle_speeds.size()) {
return;
}
_shuttle_speeds[index] = speed;
}
void
ContourDesignControlProtocol::shuttle_event (int position)
{
if (abs(position) > num_shuttle_speeds) {
DEBUG_TRACE (DEBUG::ContourDesignControl, "received invalid shuttle position... ignoring.\n");
return;
}
if (position != 0) {
if (_shuttle_was_zero) {
_was_rolling_before_shuttle = transport_rolling ();
}
const vector<double>& spds = _shuttle_speeds;
const double speed = position > 0 ? spds[position-1] : -spds[-position-1];
set_transport_speed (speed);
_shuttle_was_zero = false;
} else {
if (_keep_rolling && _was_rolling_before_shuttle) {
set_transport_speed (1.0);
} else {
transport_stop ();
}
_shuttle_was_zero = true;
}
}
void
ButtonJump::execute ()
{
_spc.jump_forward (_dist);
}
XMLNode&
ButtonJump::get_state (XMLNode& node) const
{
string ts (X_("jump"));
node.set_property (X_("type"), ts);
node.set_property (X_("distance"), _dist.value);
string s;
switch (_dist.unit) {
case SECONDS: s = X_("seconds"); break;
case BARS: s = X_("bars"); break;
case BEATS:
default: s = X_("beats");
}
node.set_property (X_("unit"), s);
return node;
}
void
ButtonAction::execute ()
{
_spc.access_action (_action_string);
}
XMLNode&
ButtonAction::get_state (XMLNode& node) const
{
string ts (X_("action"));
node.set_property (X_("type"), ts);
node.set_property (X_("path"), _action_string);
return node;
}
static void LIBUSB_CALL event_callback (libusb_transfer* transfer)
{
ContourDesignControlProtocol* spc = static_cast<ContourDesignControlProtocol*> (transfer->user_data);
spc->handle_event();
}