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
1183 lines
28 KiB
C++
1183 lines
28 KiB
C++
/*
|
|
* Copyright (C) 2006 Paul Davis
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cerrno>
|
|
#include <algorithm>
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "pbd/gstdio_compat.h"
|
|
#include <glibmm/miscutils.h>
|
|
|
|
#include <pbd/convert.h>
|
|
#include <pbd/pthread_utils.h>
|
|
#include <pbd/file_utils.h>
|
|
#include <pbd/failed_constructor.h>
|
|
|
|
#include "ardour/amp.h"
|
|
#include "ardour/session.h"
|
|
#include "ardour/route.h"
|
|
#include "ardour/audio_track.h"
|
|
#include "ardour/midi_track.h"
|
|
#include "ardour/dB.h"
|
|
#include "ardour/filesystem_paths.h"
|
|
#include "ardour/panner.h"
|
|
#include "ardour/plugin.h"
|
|
#include "ardour/plugin_insert.h"
|
|
#include "ardour/send.h"
|
|
|
|
#include "osc.h"
|
|
#include "osc_controllable.h"
|
|
#include "osc_route_observer.h"
|
|
#include "i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace std;
|
|
using namespace Glib;
|
|
using namespace ArdourSurface;
|
|
|
|
#include "pbd/abstract_ui.cc" // instantiate template
|
|
|
|
OSC* OSC::_instance = 0;
|
|
|
|
#ifdef DEBUG
|
|
static void error_callback(int num, const char *m, const char *path)
|
|
{
|
|
fprintf(stderr, "liblo server error %d in path %s: %s\n", num, path, m);
|
|
}
|
|
#else
|
|
static void error_callback(int, const char *, const char *)
|
|
{
|
|
|
|
}
|
|
#endif
|
|
|
|
OSC::OSC (Session& s, uint32_t port)
|
|
: ControlProtocol (s, X_("Open Sound Control (OSC)"))
|
|
, AbstractUI<OSCUIRequest> (name())
|
|
, local_server (0)
|
|
, remote_server (0)
|
|
, _port(port)
|
|
, _ok (true)
|
|
, _shutdown (false)
|
|
, _osc_server (0)
|
|
, _osc_unix_server (0)
|
|
, _namespace_root ("/ardour")
|
|
, _send_route_changes (true)
|
|
{
|
|
_instance = this;
|
|
|
|
session_loaded (s);
|
|
session->Exported.connect (*this, MISSING_INVALIDATOR, boost::bind (&OSC::session_exported, this, _1, _2), this);
|
|
}
|
|
|
|
OSC::~OSC()
|
|
{
|
|
stop ();
|
|
_instance = 0;
|
|
}
|
|
|
|
void*
|
|
OSC::request_factory (uint32_t num_requests)
|
|
{
|
|
/* AbstractUI<T>::request_buffer_factory() is a template method only
|
|
instantiated in this source module. To provide something visible for
|
|
use in the interface/descriptor, we have this static method that is
|
|
template-free.
|
|
*/
|
|
return request_buffer_factory (num_requests);
|
|
}
|
|
|
|
void
|
|
OSC::do_request (OSCUIRequest* req)
|
|
{
|
|
if (req->type == CallSlot) {
|
|
|
|
call_slot (MISSING_INVALIDATOR, req->the_slot);
|
|
|
|
} else if (req->type == Quit) {
|
|
|
|
stop ();
|
|
}
|
|
}
|
|
|
|
int
|
|
OSC::set_active (bool yn)
|
|
{
|
|
if (yn != active()) {
|
|
|
|
if (yn) {
|
|
if (start ()) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (stop ()) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return ControlProtocol::set_active (yn);
|
|
}
|
|
|
|
bool
|
|
OSC::get_active () const
|
|
{
|
|
return _osc_server != 0;
|
|
}
|
|
|
|
int
|
|
OSC::set_feedback (bool yn)
|
|
{
|
|
_send_route_changes = yn;
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
OSC::get_feedback () const
|
|
{
|
|
return _send_route_changes;
|
|
}
|
|
|
|
int
|
|
OSC::start ()
|
|
{
|
|
char tmpstr[255];
|
|
|
|
if (_osc_server) {
|
|
/* already started */
|
|
return 0;
|
|
}
|
|
|
|
for (int j=0; j < 20; ++j) {
|
|
snprintf(tmpstr, sizeof(tmpstr), "%d", _port);
|
|
|
|
//if ((_osc_server = lo_server_new_with_proto (tmpstr, LO_TCP, error_callback))) {
|
|
// break;
|
|
//}
|
|
|
|
if ((_osc_server = lo_server_new (tmpstr, error_callback))) {
|
|
break;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
cerr << "can't get osc at port: " << _port << endl;
|
|
#endif
|
|
_port++;
|
|
continue;
|
|
}
|
|
|
|
if (!_osc_server) {
|
|
return 1;
|
|
}
|
|
|
|
#ifdef ARDOUR_OSC_UNIX_SERVER
|
|
|
|
// APPEARS sluggish for now
|
|
|
|
// attempt to create unix socket server too
|
|
|
|
snprintf(tmpstr, sizeof(tmpstr), "/tmp/sooperlooper_XXXXXX");
|
|
int fd = mkstemp(tmpstr);
|
|
|
|
if (fd >= 0 ) {
|
|
::g_unlink (tmpstr);
|
|
close (fd);
|
|
|
|
_osc_unix_server = lo_server_new (tmpstr, error_callback);
|
|
|
|
if (_osc_unix_server) {
|
|
_osc_unix_socket_path = tmpstr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
PBD::info << "OSC @ " << get_server_url () << endmsg;
|
|
|
|
std::string url_file;
|
|
|
|
if (find_file (ardour_config_search_path(), "osc_url", url_file)) {
|
|
_osc_url_file = url_file;
|
|
if (g_file_set_contents (_osc_url_file.c_str(), get_server_url().c_str(), -1, NULL)) {
|
|
cerr << "Couldn't write '" << _osc_url_file << "'" <<endl;
|
|
}
|
|
}
|
|
|
|
register_callbacks();
|
|
|
|
// lo_server_thread_add_method(_sthread, NULL, NULL, OSC::_dummy_handler, this);
|
|
|
|
/* startup the event loop thread */
|
|
|
|
BaseUI::run ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
OSC::thread_init ()
|
|
{
|
|
pthread_set_name (event_loop_name().c_str());
|
|
|
|
if (_osc_unix_server) {
|
|
Glib::RefPtr<IOSource> src = IOSource::create (lo_server_get_socket_fd (_osc_unix_server), IO_IN|IO_HUP|IO_ERR);
|
|
src->connect (sigc::bind (sigc::mem_fun (*this, &OSC::osc_input_handler), _osc_unix_server));
|
|
src->attach (_main_loop->get_context());
|
|
local_server = src->gobj();
|
|
g_source_ref (local_server);
|
|
}
|
|
|
|
if (_osc_server) {
|
|
Glib::RefPtr<IOSource> src = IOSource::create (lo_server_get_socket_fd (_osc_server), IO_IN|IO_HUP|IO_ERR);
|
|
src->connect (sigc::bind (sigc::mem_fun (*this, &OSC::osc_input_handler), _osc_server));
|
|
src->attach (_main_loop->get_context());
|
|
remote_server = src->gobj();
|
|
g_source_ref (remote_server);
|
|
}
|
|
|
|
PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
|
|
SessionEvent::create_per_thread_pool (event_loop_name(), 128);
|
|
}
|
|
|
|
int
|
|
OSC::stop ()
|
|
{
|
|
/* stop main loop */
|
|
|
|
if (local_server) {
|
|
g_source_destroy (local_server);
|
|
g_source_unref (local_server);
|
|
local_server = 0;
|
|
}
|
|
|
|
if (remote_server) {
|
|
g_source_destroy (remote_server);
|
|
g_source_unref (remote_server);
|
|
remote_server = 0;
|
|
}
|
|
|
|
BaseUI::quit ();
|
|
|
|
if (_osc_server) {
|
|
lo_server_free (_osc_server);
|
|
_osc_server = 0;
|
|
}
|
|
|
|
if (_osc_unix_server) {
|
|
lo_server_free (_osc_unix_server);
|
|
_osc_unix_server = 0;
|
|
}
|
|
|
|
if (!_osc_unix_socket_path.empty()) {
|
|
::g_unlink (_osc_unix_socket_path.c_str());
|
|
}
|
|
|
|
if (!_osc_url_file.empty() ) {
|
|
::g_unlink (_osc_url_file.c_str() );
|
|
}
|
|
|
|
// Delete any active route observers
|
|
for (RouteObservers::iterator x = route_observers.begin(); x != route_observers.end();) {
|
|
|
|
OSCRouteObserver* rc;
|
|
|
|
if ((rc = dynamic_cast<OSCRouteObserver*>(*x)) != 0) {
|
|
delete *x;
|
|
x = route_observers.erase (x);
|
|
} else {
|
|
++x;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
OSC::register_callbacks()
|
|
{
|
|
lo_server srvs[2];
|
|
lo_server serv;
|
|
|
|
srvs[0] = _osc_server;
|
|
srvs[1] = _osc_unix_server;
|
|
|
|
for (size_t i = 0; i < 2; ++i) {
|
|
|
|
if (!srvs[i]) {
|
|
continue;
|
|
}
|
|
|
|
serv = srvs[i];
|
|
|
|
/* this is a special catchall handler */
|
|
|
|
lo_server_add_method (serv, 0, 0, _catchall, this);
|
|
|
|
#define REGISTER_CALLBACK(serv,path,types, function) lo_server_add_method (serv, path, types, OSC::_ ## function, this)
|
|
|
|
REGISTER_CALLBACK (serv, "/routes/list", "", routes_list);
|
|
REGISTER_CALLBACK (serv, "/ardour/add_marker", "", add_marker);
|
|
REGISTER_CALLBACK (serv, "/ardour/access_action", "s", access_action);
|
|
REGISTER_CALLBACK (serv, "/ardour/loop_toggle", "", loop_toggle);
|
|
REGISTER_CALLBACK (serv, "/ardour/loop_location", "ii", loop_location);
|
|
REGISTER_CALLBACK (serv, "/ardour/goto_start", "", goto_start);
|
|
REGISTER_CALLBACK (serv, "/ardour/goto_end", "", goto_end);
|
|
REGISTER_CALLBACK (serv, "/ardour/rewind", "", rewind);
|
|
REGISTER_CALLBACK (serv, "/ardour/ffwd", "", ffwd);
|
|
REGISTER_CALLBACK (serv, "/ardour/transport_stop", "", transport_stop);
|
|
REGISTER_CALLBACK (serv, "/ardour/transport_play", "", transport_play);
|
|
REGISTER_CALLBACK (serv, "/ardour/transport_frame", "", transport_frame);
|
|
REGISTER_CALLBACK (serv, "/ardour/transport_speed", "", transport_speed);
|
|
REGISTER_CALLBACK (serv, "/ardour/record_enabled", "", record_enabled);
|
|
REGISTER_CALLBACK (serv, "/ardour/set_transport_speed", "f", set_transport_speed);
|
|
REGISTER_CALLBACK (serv, "/ardour/locate", "ii", locate);
|
|
REGISTER_CALLBACK (serv, "/ardour/save_state", "", save_state);
|
|
REGISTER_CALLBACK (serv, "/ardour/prev_marker", "", prev_marker);
|
|
REGISTER_CALLBACK (serv, "/ardour/next_marker", "", next_marker);
|
|
REGISTER_CALLBACK (serv, "/ardour/undo", "", undo);
|
|
REGISTER_CALLBACK (serv, "/ardour/redo", "", redo);
|
|
REGISTER_CALLBACK (serv, "/ardour/toggle_punch_in", "", toggle_punch_in);
|
|
REGISTER_CALLBACK (serv, "/ardour/toggle_punch_out", "", toggle_punch_out);
|
|
REGISTER_CALLBACK (serv, "/ardour/rec_enable_toggle", "", rec_enable_toggle);
|
|
REGISTER_CALLBACK (serv, "/ardour/toggle_all_rec_enables", "", toggle_all_rec_enables);
|
|
|
|
/*
|
|
* NOTE: these messages are provided for (arguably broken) apps
|
|
* that MUST send float args ( TouchOSC and Lemur ).
|
|
* Normally these ardour transport messages don't require an argument,
|
|
* so we're providing redundant calls with vestigial "float" args.
|
|
*
|
|
* Is it really useful to ignore the parameter?
|
|
* http://hexler.net/docs/touchosc-controls-reference suggests that
|
|
* push buttons do send 0,1. We will have to provide semantic equivalents
|
|
* rather than simply ignore the parameter.
|
|
* e.g push & release the button will send
|
|
* .../undo f 1
|
|
* .../undo f 0
|
|
* resulting in two undos.
|
|
*/
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/loop_toggle", "f", loop_toggle);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/add_marker", "f", add_marker);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/goto_start", "f", goto_start);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/goto_end", "f", goto_end);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/rewind", "f", rewind);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/ffwd", "f", ffwd);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/transport_stop", "f", transport_stop);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/transport_play", "f", transport_play);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/save_state", "f", save_state);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/prev_marker", "f", prev_marker);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/next_marker", "f", next_marker);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/undo", "f", undo);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/redo", "f", redo);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/toggle_punch_in", "f", toggle_punch_in);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/toggle_punch_out", "f", toggle_punch_out);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/rec_enable_toggle", "f", rec_enable_toggle);
|
|
REGISTER_CALLBACK (serv, "/ardour/unused_argument/toggle_all_rec_enables", "f", toggle_all_rec_enables);
|
|
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/mute", "ii", route_mute);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/solo", "ii", route_solo);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/recenable", "ii", route_recenable);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/gainabs", "if", route_set_gain_abs);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/gaindB", "if", route_set_gain_dB);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/trimabs", "if", route_set_trim_abs);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/trimdB", "if", route_set_trim_dB);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/pan_stereo_position", "if", route_set_pan_stereo_position);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/pan_stereo_width", "if", route_set_pan_stereo_width);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/plugin/parameter", "iiif", route_plugin_parameter);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/plugin/parameter/print", "iii", route_plugin_parameter_print);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/send/gainabs", "iif", route_set_send_gain_abs);
|
|
REGISTER_CALLBACK (serv, "/ardour/routes/send/gaindB", "iif", route_set_send_gain_dB);
|
|
|
|
/* still not-really-standardized query interface */
|
|
//REGISTER_CALLBACK (serv, "/ardour/*/#current_value", "", current_value);
|
|
//REGISTER_CALLBACK (serv, "/ardour/set", "", set);
|
|
|
|
// un/register_update args= s:ctrl s:returl s:retpath
|
|
//lo_server_add_method(serv, "/register_update", "sss", OSC::global_register_update_handler, this);
|
|
//lo_server_add_method(serv, "/unregister_update", "sss", OSC::global_unregister_update_handler, this);
|
|
//lo_server_add_method(serv, "/register_auto_update", "siss", OSC::global_register_auto_update_handler, this);
|
|
//lo_server_add_method(serv, "/unregister_auto_update", "sss", OSC::_global_unregister_auto_update_handler, this);
|
|
|
|
}
|
|
}
|
|
|
|
bool
|
|
OSC::osc_input_handler (IOCondition ioc, lo_server srv)
|
|
{
|
|
if (ioc & ~IO_IN) {
|
|
return false;
|
|
}
|
|
|
|
if (ioc & IO_IN) {
|
|
lo_server_recv (srv);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string
|
|
OSC::get_server_url()
|
|
{
|
|
string url;
|
|
char * urlstr;
|
|
|
|
if (_osc_server) {
|
|
urlstr = lo_server_get_url (_osc_server);
|
|
url = urlstr;
|
|
free (urlstr);
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
std::string
|
|
OSC::get_unix_server_url()
|
|
{
|
|
string url;
|
|
char * urlstr;
|
|
|
|
if (_osc_unix_server) {
|
|
urlstr = lo_server_get_url (_osc_unix_server);
|
|
url = urlstr;
|
|
free (urlstr);
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
void
|
|
OSC::listen_to_route (boost::shared_ptr<Route> route, lo_address addr)
|
|
{
|
|
/* avoid duplicate listens */
|
|
|
|
for (RouteObservers::iterator x = route_observers.begin(); x != route_observers.end(); ++x) {
|
|
|
|
OSCRouteObserver* ro;
|
|
|
|
if ((ro = dynamic_cast<OSCRouteObserver*>(*x)) != 0) {
|
|
|
|
int res = strcmp(lo_address_get_hostname(ro->address()), lo_address_get_hostname(addr));
|
|
|
|
if (ro->route() == route && res == 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
OSCRouteObserver* o = new OSCRouteObserver (route, addr);
|
|
route_observers.push_back (o);
|
|
|
|
route->DropReferences.connect (*this, MISSING_INVALIDATOR, boost::bind (&OSC::drop_route, this, boost::weak_ptr<Route> (route)), this);
|
|
}
|
|
|
|
void
|
|
OSC::drop_route (boost::weak_ptr<Route> wr)
|
|
{
|
|
boost::shared_ptr<Route> r = wr.lock ();
|
|
|
|
if (!r) {
|
|
return;
|
|
}
|
|
|
|
for (RouteObservers::iterator x = route_observers.begin(); x != route_observers.end();) {
|
|
|
|
OSCRouteObserver* rc;
|
|
|
|
if ((rc = dynamic_cast<OSCRouteObserver*>(*x)) != 0) {
|
|
|
|
if (rc->route() == r) {
|
|
delete *x;
|
|
x = route_observers.erase (x);
|
|
} else {
|
|
++x;
|
|
}
|
|
} else {
|
|
++x;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
OSC::end_listen (boost::shared_ptr<Route> r, lo_address addr)
|
|
{
|
|
RouteObservers::iterator x;
|
|
|
|
// Remove the route observers
|
|
for (x = route_observers.begin(); x != route_observers.end();) {
|
|
|
|
OSCRouteObserver* ro;
|
|
|
|
if ((ro = dynamic_cast<OSCRouteObserver*>(*x)) != 0) {
|
|
|
|
int res = strcmp(lo_address_get_hostname(ro->address()), lo_address_get_hostname(addr));
|
|
|
|
if (ro->route() == r && res == 0) {
|
|
delete *x;
|
|
x = route_observers.erase (x);
|
|
}
|
|
else {
|
|
++x;
|
|
}
|
|
}
|
|
else {
|
|
++x;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
OSC::current_value_query (const char* path, size_t len, lo_arg **argv, int argc, lo_message msg)
|
|
{
|
|
char* subpath;
|
|
|
|
subpath = (char*) malloc (len-15+1);
|
|
memcpy (subpath, path, len-15);
|
|
subpath[len-15] = '\0';
|
|
|
|
send_current_value (subpath, argv, argc, msg);
|
|
|
|
free (subpath);
|
|
}
|
|
|
|
void
|
|
OSC::send_current_value (const char* path, lo_arg** argv, int argc, lo_message msg)
|
|
{
|
|
if (!session) {
|
|
return;
|
|
}
|
|
|
|
lo_message reply = lo_message_new ();
|
|
boost::shared_ptr<Route> r;
|
|
int id;
|
|
|
|
lo_message_add_string (reply, path);
|
|
|
|
if (argc == 0) {
|
|
lo_message_add_string (reply, "bad syntax");
|
|
} else {
|
|
id = argv[0]->i;
|
|
r = session->route_by_remote_id (id);
|
|
|
|
if (!r) {
|
|
lo_message_add_string (reply, "not found");
|
|
} else {
|
|
|
|
if (strcmp (path, "/routes/state") == 0) {
|
|
|
|
if (boost::dynamic_pointer_cast<AudioTrack>(r)) {
|
|
lo_message_add_string (reply, "AT");
|
|
} else if (boost::dynamic_pointer_cast<MidiTrack>(r)) {
|
|
lo_message_add_string (reply, "MT");
|
|
} else {
|
|
lo_message_add_string (reply, "B");
|
|
}
|
|
|
|
lo_message_add_string (reply, r->name().c_str());
|
|
lo_message_add_int32 (reply, r->n_inputs().n_audio());
|
|
lo_message_add_int32 (reply, r->n_outputs().n_audio());
|
|
lo_message_add_int32 (reply, r->muted());
|
|
lo_message_add_int32 (reply, r->soloed());
|
|
|
|
} else if (strcmp (path, "/routes/mute") == 0) {
|
|
|
|
lo_message_add_int32 (reply, (float) r->muted());
|
|
|
|
} else if (strcmp (path, "/routes/solo") == 0) {
|
|
|
|
lo_message_add_int32 (reply, r->soloed());
|
|
}
|
|
}
|
|
}
|
|
|
|
lo_send_message (lo_message_get_source (msg), "#reply", reply);
|
|
lo_message_free (reply);
|
|
}
|
|
|
|
int
|
|
OSC::_catchall (const char *path, const char *types, lo_arg **argv, int argc, void *data, void *user_data)
|
|
{
|
|
return ((OSC*)user_data)->catchall (path, types, argv, argc, data);
|
|
}
|
|
|
|
int
|
|
OSC::catchall (const char *path, const char* /*types*/, lo_arg **argv, int argc, lo_message msg)
|
|
{
|
|
size_t len;
|
|
int ret = 1; /* unhandled */
|
|
|
|
//cerr << "Received a message, path = " << path << " types = \""
|
|
// << (types ? types : "NULL") << '"' << endl;
|
|
|
|
/* 15 for /#current_value plus 2 for /<path> */
|
|
|
|
len = strlen (path);
|
|
|
|
if (len >= 17 && !strcmp (&path[len-15], "/#current_value")) {
|
|
current_value_query (path, len, argv, argc, msg);
|
|
ret = 0;
|
|
|
|
} else if (strcmp (path, "/routes/listen") == 0) {
|
|
|
|
cerr << "set up listener\n";
|
|
|
|
lo_message reply = lo_message_new ();
|
|
|
|
if (argc <= 0) {
|
|
lo_message_add_string (reply, "syntax error");
|
|
} else {
|
|
for (int n = 0; n < argc; ++n) {
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (argv[n]->i);
|
|
|
|
if (!r) {
|
|
lo_message_add_string (reply, "not found");
|
|
cerr << "no such route\n";
|
|
break;
|
|
} else {
|
|
cerr << "add listener\n";
|
|
listen_to_route (r, lo_message_get_source (msg));
|
|
lo_message_add_int32 (reply, argv[n]->i);
|
|
}
|
|
}
|
|
}
|
|
|
|
lo_send_message (lo_message_get_source (msg), "#reply", reply);
|
|
lo_message_free (reply);
|
|
|
|
ret = 0;
|
|
|
|
} else if (strcmp (path, "/routes/ignore") == 0) {
|
|
|
|
for (int n = 0; n < argc; ++n) {
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (argv[n]->i);
|
|
|
|
if (r) {
|
|
end_listen (r, lo_message_get_source (msg));
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
OSC::update_clock ()
|
|
{
|
|
|
|
}
|
|
|
|
// "Application Hook" Handlers //
|
|
void
|
|
OSC::session_loaded (Session& s)
|
|
{
|
|
lo_address listener = lo_address_new (NULL, "7770");
|
|
lo_send (listener, "/session/loaded", "ss", s.path().c_str(), s.name().c_str());
|
|
}
|
|
|
|
void
|
|
OSC::session_exported (std::string path, std::string name)
|
|
{
|
|
lo_address listener = lo_address_new (NULL, "7770");
|
|
lo_send (listener, "/session/exported", "ss", path.c_str(), name.c_str());
|
|
}
|
|
|
|
// end "Application Hook" Handlers //
|
|
|
|
/* path callbacks */
|
|
|
|
int
|
|
OSC::current_value (const char */*path*/, const char */*types*/, lo_arg **/*argv*/, int /*argc*/, void */*data*/, void* /*user_data*/)
|
|
{
|
|
#if 0
|
|
const char* returl;
|
|
|
|
if (argc < 3 || types == 0 || strlen (types) < 3 || types[0] != 's' || types[1] != 's' || types[2] != s) {
|
|
return 1;
|
|
}
|
|
|
|
const char *returl = argv[1]->s;
|
|
lo_address addr = find_or_cache_addr (returl);
|
|
|
|
const char *retpath = argv[2]->s;
|
|
|
|
|
|
if (strcmp (argv[0]->s, "transport_frame") == 0) {
|
|
|
|
if (session) {
|
|
lo_send (addr, retpath, "i", session->transport_frame());
|
|
}
|
|
|
|
} else if (strcmp (argv[0]->s, "transport_speed") == 0) {
|
|
|
|
if (session) {
|
|
lo_send (addr, retpath, "i", session->transport_frame());
|
|
}
|
|
|
|
} else if (strcmp (argv[0]->s, "transport_locked") == 0) {
|
|
|
|
if (session) {
|
|
lo_send (addr, retpath, "i", session->transport_frame());
|
|
}
|
|
|
|
} else if (strcmp (argv[0]->s, "punch_in") == 0) {
|
|
|
|
if (session) {
|
|
lo_send (addr, retpath, "i", session->transport_frame());
|
|
}
|
|
|
|
} else if (strcmp (argv[0]->s, "punch_out") == 0) {
|
|
|
|
if (session) {
|
|
lo_send (addr, retpath, "i", session->transport_frame());
|
|
}
|
|
|
|
} else if (strcmp (argv[0]->s, "rec_enable") == 0) {
|
|
|
|
if (session) {
|
|
lo_send (addr, retpath, "i", session->transport_frame());
|
|
}
|
|
|
|
} else {
|
|
|
|
/* error */
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
OSC::routes_list (lo_message msg)
|
|
{
|
|
if (!session) {
|
|
return;
|
|
}
|
|
for (int n = 0; n < (int) session->nroutes(); ++n) {
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (n);
|
|
|
|
if (r) {
|
|
|
|
lo_message reply = lo_message_new ();
|
|
|
|
if (boost::dynamic_pointer_cast<AudioTrack>(r)) {
|
|
lo_message_add_string (reply, "AT");
|
|
} else if (boost::dynamic_pointer_cast<MidiTrack>(r)) {
|
|
lo_message_add_string (reply, "MT");
|
|
} else {
|
|
lo_message_add_string (reply, "B");
|
|
}
|
|
|
|
lo_message_add_string (reply, r->name().c_str());
|
|
lo_message_add_int32 (reply, r->n_inputs().n_audio());
|
|
lo_message_add_int32 (reply, r->n_outputs().n_audio());
|
|
lo_message_add_int32 (reply, r->muted());
|
|
lo_message_add_int32 (reply, r->soloed());
|
|
lo_message_add_int32 (reply, r->remote_control_id());
|
|
|
|
if (boost::dynamic_pointer_cast<AudioTrack>(r)
|
|
|| boost::dynamic_pointer_cast<MidiTrack>(r)) {
|
|
|
|
boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(r);
|
|
lo_message_add_int32 (reply, t->record_enabled());
|
|
}
|
|
|
|
//Automatically listen to routes listed
|
|
listen_to_route(r, lo_message_get_source (msg));
|
|
|
|
lo_send_message (lo_message_get_source (msg), "#reply", reply);
|
|
lo_message_free (reply);
|
|
}
|
|
}
|
|
|
|
// Send end of listing message
|
|
lo_message reply = lo_message_new ();
|
|
|
|
lo_message_add_string (reply, "end_route_list");
|
|
lo_message_add_int64 (reply, session->frame_rate());
|
|
lo_message_add_int64 (reply, session->current_end_frame());
|
|
|
|
lo_send_message (lo_message_get_source (msg), "#reply", reply);
|
|
|
|
lo_message_free (reply);
|
|
}
|
|
|
|
void
|
|
OSC::transport_frame (lo_message msg)
|
|
{
|
|
if (!session) {
|
|
return;
|
|
}
|
|
framepos_t pos = session->transport_frame ();
|
|
|
|
lo_message reply = lo_message_new ();
|
|
lo_message_add_int64 (reply, pos);
|
|
|
|
lo_send_message (lo_message_get_source (msg), "/ardour/transport_frame", reply);
|
|
|
|
lo_message_free (reply);
|
|
}
|
|
|
|
void
|
|
OSC::transport_speed (lo_message msg)
|
|
{
|
|
if (!session) {
|
|
return;
|
|
}
|
|
double ts = session->transport_speed ();
|
|
|
|
lo_message reply = lo_message_new ();
|
|
lo_message_add_double (reply, ts);
|
|
|
|
lo_send_message (lo_message_get_source (msg), "/ardour/transport_speed", reply);
|
|
|
|
lo_message_free (reply);
|
|
}
|
|
|
|
void
|
|
OSC::record_enabled (lo_message msg)
|
|
{
|
|
if (!session) {
|
|
return;
|
|
}
|
|
int re = (int)session->get_record_enabled ();
|
|
|
|
lo_message reply = lo_message_new ();
|
|
lo_message_add_int32 (reply, re);
|
|
|
|
lo_send_message (lo_message_get_source (msg), "/ardour/record_enabled", reply);
|
|
|
|
lo_message_free (reply);
|
|
}
|
|
|
|
|
|
int
|
|
OSC::route_mute (int rid, int yn)
|
|
{
|
|
if (!session) return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (r) {
|
|
r->set_mute (yn, this);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_solo (int rid, int yn)
|
|
{
|
|
if (!session) return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (r) {
|
|
boost::shared_ptr<RouteList> rl (new RouteList);
|
|
rl->push_back (r);
|
|
session->set_solo (rl, yn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_recenable (int rid, int yn)
|
|
{
|
|
if (!session) return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (r) {
|
|
r->set_record_enabled (yn, this);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_set_gain_abs (int rid, float level)
|
|
{
|
|
if (!session) return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (r) {
|
|
r->set_gain (level, this);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_set_gain_dB (int rid, float dB)
|
|
{
|
|
return route_set_gain_abs (rid, dB_to_coefficient (dB));
|
|
}
|
|
|
|
|
|
int
|
|
OSC::route_set_trim_abs (int rid, float level)
|
|
{
|
|
if (!session) return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (r) {
|
|
r->set_trim (level, this);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_set_trim_dB (int rid, float dB)
|
|
{
|
|
return route_set_trim_abs(rid, dB_to_coefficient (dB));
|
|
}
|
|
|
|
|
|
int
|
|
OSC::route_set_pan_stereo_position (int rid, float pos)
|
|
{
|
|
if (!session) return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (r) {
|
|
boost::shared_ptr<Panner> panner = r->panner();
|
|
if (panner) {
|
|
panner->set_position (pos);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int
|
|
OSC::route_set_pan_stereo_width (int rid, float pos)
|
|
{
|
|
if (!session) return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (r) {
|
|
boost::shared_ptr<Panner> panner = r->panner();
|
|
if (panner) {
|
|
panner->set_width (pos);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int
|
|
OSC::route_set_send_gain_abs (int rid, int sid, float val)
|
|
{
|
|
if (!session) {
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (!r) {
|
|
return -1;
|
|
}
|
|
|
|
/* revert to zero-based counting */
|
|
|
|
if (sid > 0) {
|
|
--sid;
|
|
}
|
|
|
|
boost::shared_ptr<Processor> p = r->nth_send (sid);
|
|
|
|
if (p) {
|
|
boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(p);
|
|
boost::shared_ptr<Amp> a = s->amp();
|
|
|
|
if (a) {
|
|
a->set_gain (val, this);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_set_send_gain_dB (int rid, int sid, float val)
|
|
{
|
|
if (!session) {
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (!r) {
|
|
return -1;
|
|
}
|
|
|
|
/* revert to zero-based counting */
|
|
|
|
if (sid > 0) {
|
|
--sid;
|
|
}
|
|
|
|
boost::shared_ptr<Processor> p = r->nth_send (sid);
|
|
|
|
if (p) {
|
|
boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(p);
|
|
boost::shared_ptr<Amp> a = s->amp();
|
|
|
|
if (a) {
|
|
a->set_gain (dB_to_coefficient (val), this);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_plugin_parameter (int rid, int piid, int par, float val)
|
|
{
|
|
if (!session)
|
|
return -1;
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (!r) {
|
|
PBD::error << "OSC: Invalid Remote Control ID '" << rid << "'" << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<Processor> redi=r->nth_plugin (piid);
|
|
|
|
if (!redi) {
|
|
PBD::error << "OSC: cannot find plugin # " << piid << " for RID '" << rid << "'" << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<PluginInsert> pi;
|
|
|
|
if (!(pi = boost::dynamic_pointer_cast<PluginInsert>(redi))) {
|
|
PBD::error << "OSC: given processor # " << piid << " on RID '" << rid << "' is not a Plugin." << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<ARDOUR::Plugin> pip = pi->plugin();
|
|
bool ok=false;
|
|
|
|
uint32_t controlid = pip->nth_parameter (par,ok);
|
|
|
|
if (!ok) {
|
|
PBD::error << "OSC: Cannot find parameter # " << par << " for plugin # " << piid << " on RID '" << rid << "'" << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
if (!pip->parameter_is_input(controlid)) {
|
|
PBD::error << "OSC: Parameter # " << par << " for plugin # " << piid << " on RID '" << rid << "' is not a control input" << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
ParameterDescriptor pd;
|
|
pi->plugin()->get_parameter_descriptor (controlid,pd);
|
|
|
|
if (val >= pd.lower && val < pd.upper) {
|
|
|
|
boost::shared_ptr<AutomationControl> c = pi->automation_control (Evoral::Parameter(PluginAutomation, 0, controlid));
|
|
// cerr << "parameter:" << redi->describe_parameter(controlid) << " val:" << val << "\n";
|
|
c->set_value (val);
|
|
} else {
|
|
PBD::warning << "OSC: Parameter # " << par << " for plugin # " << piid << " on RID '" << rid << "' is out of range" << endmsg;
|
|
PBD::info << "OSC: Valid range min=" << pd.lower << " max=" << pd.upper << endmsg;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
OSC::route_plugin_parameter_print (int rid, int piid, int par)
|
|
{
|
|
if (!session) {
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<Route> r = session->route_by_remote_id (rid);
|
|
|
|
if (!r) {
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<Processor> redi=r->nth_processor (piid);
|
|
|
|
if (!redi) {
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<PluginInsert> pi;
|
|
|
|
if (!(pi = boost::dynamic_pointer_cast<PluginInsert>(redi))) {
|
|
return -1;
|
|
}
|
|
|
|
boost::shared_ptr<ARDOUR::Plugin> pip = pi->plugin();
|
|
bool ok=false;
|
|
|
|
uint32_t controlid = pip->nth_parameter (par,ok);
|
|
|
|
if (!ok) {
|
|
return -1;
|
|
}
|
|
|
|
ParameterDescriptor pd;
|
|
|
|
if (pi->plugin()->get_parameter_descriptor (controlid, pd) == 0) {
|
|
boost::shared_ptr<AutomationControl> c = pi->automation_control (Evoral::Parameter(PluginAutomation, 0, controlid));
|
|
|
|
cerr << "parameter: " << redi->describe_parameter(controlid) << "\n";
|
|
cerr << "current value: " << c->get_value ();
|
|
cerr << "lower value: " << pd.lower << "\n";
|
|
cerr << "upper value: " << pd.upper << "\n";
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
XMLNode&
|
|
OSC::get_state ()
|
|
{
|
|
return ControlProtocol::get_state();
|
|
}
|
|
|
|
int
|
|
OSC::set_state (const XMLNode& node, int version)
|
|
{
|
|
if (ControlProtocol::set_state (node, version)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|