Add websockets surface module
This commit is contained in:
committed by
Robin Gareus
parent
4416530929
commit
8db9755d1e
132
libs/surfaces/websockets/ardour_websockets.cc
Normal file
132
libs/surfaces/websockets/ardour_websockets.cc
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
|
||||
* Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
|
||||
* Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
|
||||
* Copyright (C) 2012-2016 Tim Mayberry <mojofunk@gmail.com>
|
||||
* Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2015-2016 Ben Loftis <ben@harrisonconsoles.com>
|
||||
* Copyright (C) 2015-2018 John Emmas <john@creativepost.co.uk>
|
||||
* Copyright (C) 2015 Johannes Mueller <github@johannes-mueller.org>
|
||||
* Copyright (C) 2016-2018 Len Ovens <len@ovenwerks.net>
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 "ardour/session.h"
|
||||
|
||||
#include "ardour_websockets.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
using namespace ArdourSurface;
|
||||
|
||||
#include "pbd/abstract_ui.cc" // instantiate template
|
||||
|
||||
ArdourWebsockets::ArdourWebsockets (Session& s)
|
||||
: ControlProtocol (s, X_(SURFACE_NAME))
|
||||
, AbstractUI<ArdourWebsocketsUIRequest> (name ())
|
||||
, _strips (*this)
|
||||
, _globals (*this)
|
||||
, _feedback (*this)
|
||||
, _server (*this)
|
||||
, _dispatcher (*this)
|
||||
{
|
||||
_components = { &_strips, &_globals, &_server, &_feedback, &_dispatcher };
|
||||
}
|
||||
|
||||
ArdourWebsockets::~ArdourWebsockets ()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void*
|
||||
ArdourWebsockets::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);
|
||||
}
|
||||
|
||||
int
|
||||
ArdourWebsockets::set_active (bool yn)
|
||||
{
|
||||
if (yn != active ()) {
|
||||
if (yn) {
|
||||
if (start ()) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (stop ()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ControlProtocol::set_active (yn);
|
||||
}
|
||||
|
||||
void
|
||||
ArdourWebsockets::thread_init ()
|
||||
{
|
||||
pthread_set_name (event_loop_name ().c_str ());
|
||||
PBD::notify_event_loops_about_thread_creation (pthread_self (), event_loop_name (), 2048);
|
||||
SessionEvent::create_per_thread_pool (event_loop_name (), 128);
|
||||
}
|
||||
|
||||
void
|
||||
ArdourWebsockets::do_request (ArdourWebsocketsUIRequest* req)
|
||||
{
|
||||
if (req->type == CallSlot) {
|
||||
call_slot (MISSING_INVALIDATOR, req->the_slot);
|
||||
} else if (req->type == Quit) {
|
||||
stop ();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ArdourWebsockets::start ()
|
||||
{
|
||||
// startup the event loop thread
|
||||
BaseUI::run ();
|
||||
|
||||
for (std::vector<SurfaceComponent *>::iterator it = _components.begin ();
|
||||
it != _components.end (); ++it) {
|
||||
int rc = (*it)->start ();
|
||||
if (rc != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
PBD::info << "ArdourWebsockets: started" << endmsg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ArdourWebsockets::stop () {
|
||||
for (std::vector<SurfaceComponent *>::iterator it = _components.begin ();
|
||||
it != _components.end (); ++it) {
|
||||
(*it)->stop ();
|
||||
}
|
||||
|
||||
BaseUI::quit ();
|
||||
|
||||
PBD::info << "ArdourWebsockets: stopped" << endmsg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
98
libs/surfaces/websockets/ardour_websockets.h
Normal file
98
libs/surfaces/websockets/ardour_websockets.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
|
||||
* Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
|
||||
* Copyright (C) 2009 David Robillard <d@drobilla.net>
|
||||
* Copyright (C) 2013-2016 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2015 Johannes Mueller <github@johannes-mueller.org>
|
||||
* Copyright (C) 2016-2018 Len Ovens <len@ovenwerks.net>
|
||||
* Copyright (C) 2016 Ben Loftis <ben@harrisonconsoles.com>
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ardour_websockets_h
|
||||
#define ardour_websockets_h
|
||||
|
||||
#define ABSTRACT_UI_EXPORTS
|
||||
#include "pbd/abstract_ui.h"
|
||||
|
||||
#include "ardour/plugin.h"
|
||||
#include "control_protocol/control_protocol.h"
|
||||
|
||||
#include "component.h"
|
||||
#include "strips.h"
|
||||
#include "globals.h"
|
||||
#include "server.h"
|
||||
#include "feedback.h"
|
||||
#include "dispatcher.h"
|
||||
|
||||
#define SURFACE_NAME "WebSockets Server (Experimental)"
|
||||
#define SURFACE_ID "uri://ardour.org/surfaces/ardour_websockets:0"
|
||||
|
||||
namespace ArdourSurface {
|
||||
|
||||
struct ArdourWebsocketsUIRequest : public BaseUI::BaseRequestObject {
|
||||
public:
|
||||
ArdourWebsocketsUIRequest () {}
|
||||
~ArdourWebsocketsUIRequest () {}
|
||||
};
|
||||
|
||||
class ArdourWebsockets : public ARDOUR::ControlProtocol,
|
||||
public AbstractUI<ArdourWebsocketsUIRequest>
|
||||
{
|
||||
public:
|
||||
|
||||
ArdourWebsockets (ARDOUR::Session&);
|
||||
virtual ~ArdourWebsockets ();
|
||||
|
||||
static void* request_factory (uint32_t);
|
||||
|
||||
int set_active (bool);
|
||||
|
||||
ARDOUR::Session& ardour_session () { return *session; }
|
||||
ArdourStrips& strips_component () { return _strips; }
|
||||
ArdourGlobals& globals_component () { return _globals; }
|
||||
WebsocketsServer& server_component () { return _server; }
|
||||
WebsocketsDispatcher& dispatcher_component () { return _dispatcher; }
|
||||
|
||||
// ControlProtocol
|
||||
void stripable_selection_changed () {}
|
||||
|
||||
protected:
|
||||
|
||||
// BaseUI
|
||||
void thread_init ();
|
||||
|
||||
// AbstractUI
|
||||
void do_request (ArdourWebsocketsUIRequest*);
|
||||
|
||||
private:
|
||||
|
||||
ArdourStrips _strips;
|
||||
ArdourGlobals _globals;
|
||||
ArdourFeedback _feedback;
|
||||
WebsocketsServer _server;
|
||||
WebsocketsDispatcher _dispatcher;
|
||||
std::vector<SurfaceComponent *> _components;
|
||||
|
||||
int start ();
|
||||
int stop ();
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // ardour_websockets_h
|
||||
71
libs/surfaces/websockets/client.cc
Normal file
71
libs/surfaces/websockets/client.cc
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 <sstream>
|
||||
|
||||
#include "client.h"
|
||||
|
||||
bool
|
||||
ClientContext::has_state (const NodeState& node_state)
|
||||
{
|
||||
ClientState::iterator it = _state.find (node_state);
|
||||
|
||||
if (it == _state.end ()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int n_val = node_state.n_val ();
|
||||
|
||||
if (it->n_val () != n_val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_val; i++) {
|
||||
if (it->nth_val (i) != node_state.nth_val (i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ClientContext::update_state (const NodeState& node_state)
|
||||
{
|
||||
ClientState::iterator it = _state.find (node_state);
|
||||
|
||||
if (it != _state.end ()) {
|
||||
_state.erase (it);
|
||||
}
|
||||
|
||||
_state.insert (node_state);
|
||||
}
|
||||
|
||||
std::string
|
||||
ClientContext::debug_str ()
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "client = " << std::hex << _wsi << std::endl;
|
||||
|
||||
for (ClientState::iterator it = _state.begin (); it != _state.end (); ++it) {
|
||||
ss << " - " << it->debug_str () << std::endl;
|
||||
}
|
||||
|
||||
return ss.str ();
|
||||
}
|
||||
58
libs/surfaces/websockets/client.h
Normal file
58
libs/surfaces/websockets/client.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef client_context_h
|
||||
#define client_context_h
|
||||
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
|
||||
#include "state.h"
|
||||
#include "message.h"
|
||||
|
||||
typedef struct lws* Client;
|
||||
typedef std::list<NodeStateMessage> ClientOutputBuffer;
|
||||
|
||||
class ClientContext
|
||||
{
|
||||
public:
|
||||
|
||||
ClientContext (Client wsi) : _wsi(wsi) {};
|
||||
virtual ~ClientContext () {};
|
||||
|
||||
Client wsi () const { return _wsi; }
|
||||
|
||||
bool has_state (const NodeState&);
|
||||
void update_state (const NodeState&);
|
||||
|
||||
ClientOutputBuffer& output_buf () { return _output_buf; }
|
||||
|
||||
std::string debug_str ();
|
||||
|
||||
private:
|
||||
|
||||
Client _wsi;
|
||||
|
||||
typedef std::unordered_set<NodeState> ClientState;
|
||||
ClientState _state;
|
||||
|
||||
ClientOutputBuffer _output_buf;
|
||||
|
||||
};
|
||||
|
||||
#endif // client_context_h
|
||||
62
libs/surfaces/websockets/component.cc
Normal file
62
libs/surfaces/websockets/component.cc
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 "component.h"
|
||||
#include "ardour_websockets.h"
|
||||
|
||||
PBD::EventLoop*
|
||||
SurfaceComponent::event_loop () const
|
||||
{
|
||||
return static_cast<PBD::EventLoop*>(&_surface);
|
||||
}
|
||||
|
||||
Glib::RefPtr<Glib::MainLoop>
|
||||
SurfaceComponent::main_loop () const
|
||||
{
|
||||
return _surface.main_loop ();
|
||||
}
|
||||
|
||||
ARDOUR::Session&
|
||||
SurfaceComponent::session () const
|
||||
{
|
||||
return _surface.ardour_session ();
|
||||
}
|
||||
|
||||
ArdourStrips&
|
||||
SurfaceComponent::strips () const
|
||||
{
|
||||
return _surface.strips_component ();
|
||||
}
|
||||
|
||||
ArdourGlobals&
|
||||
SurfaceComponent::globals () const
|
||||
{
|
||||
return _surface.globals_component ();
|
||||
}
|
||||
|
||||
WebsocketsServer&
|
||||
SurfaceComponent::server () const
|
||||
{
|
||||
return _surface.server_component ();
|
||||
}
|
||||
|
||||
WebsocketsDispatcher&
|
||||
SurfaceComponent::dispatcher () const
|
||||
{
|
||||
return _surface.dispatcher_component ();
|
||||
}
|
||||
61
libs/surfaces/websockets/component.h
Normal file
61
libs/surfaces/websockets/component.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef surface_component_h
|
||||
#define surface_component_h
|
||||
|
||||
#include <glibmm.h>
|
||||
|
||||
#include "ardour/session.h"
|
||||
#include "pbd/event_loop.h"
|
||||
|
||||
namespace ArdourSurface {
|
||||
class ArdourWebsockets;
|
||||
}
|
||||
|
||||
class ArdourStrips;
|
||||
class ArdourGlobals;
|
||||
class WebsocketsServer;
|
||||
class WebsocketsDispatcher;
|
||||
|
||||
class SurfaceComponent
|
||||
{
|
||||
public:
|
||||
|
||||
SurfaceComponent (ArdourSurface::ArdourWebsockets& surface) : _surface (surface) {};
|
||||
|
||||
virtual ~SurfaceComponent () {};
|
||||
|
||||
virtual int start () { return 0; }
|
||||
virtual int stop () { return 0; }
|
||||
|
||||
PBD::EventLoop* event_loop () const;
|
||||
Glib::RefPtr<Glib::MainLoop> main_loop() const;
|
||||
ARDOUR::Session& session () const;
|
||||
ArdourStrips& strips () const;
|
||||
ArdourGlobals& globals () const;
|
||||
WebsocketsServer& server () const;
|
||||
WebsocketsDispatcher& dispatcher () const;
|
||||
|
||||
protected:
|
||||
|
||||
ArdourSurface::ArdourWebsockets& _surface;
|
||||
|
||||
};
|
||||
|
||||
#endif // surface_component_h
|
||||
194
libs/surfaces/websockets/dispatcher.cc
Normal file
194
libs/surfaces/websockets/dispatcher.cc
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 "ardour/plugin_insert.h"
|
||||
|
||||
#include "dispatcher.h"
|
||||
#include "ardour_websockets.h"
|
||||
#include "state.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
#define NODE_METHOD_PAIR(x) { Node::x, &WebsocketsDispatcher::x ## _handler }
|
||||
|
||||
WebsocketsDispatcher::NodeMethodMap
|
||||
WebsocketsDispatcher::_node_to_method = {
|
||||
NODE_METHOD_PAIR(tempo),
|
||||
NODE_METHOD_PAIR(strip_gain),
|
||||
NODE_METHOD_PAIR(strip_pan),
|
||||
NODE_METHOD_PAIR(strip_mute),
|
||||
NODE_METHOD_PAIR(strip_plugin_enable),
|
||||
NODE_METHOD_PAIR(strip_plugin_param_value)
|
||||
};
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::dispatch (Client client, const NodeStateMessage& msg)
|
||||
{
|
||||
NodeMethodMap::iterator it = _node_to_method.find (msg.state ().node ());
|
||||
if (it != _node_to_method.end ()) {
|
||||
try {
|
||||
(this->*it->second) (client, msg);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::update_all_nodes (Client client)
|
||||
{
|
||||
update (client, Node::tempo, {}, { globals ().tempo () });
|
||||
|
||||
for (uint32_t strip_n = 0; strip_n < strips ().strip_count (); ++strip_n) {
|
||||
boost::shared_ptr<Stripable> strip = strips ().nth_strip (strip_n);
|
||||
boost::shared_ptr<Route> route = boost::dynamic_pointer_cast<Route> (strip);
|
||||
if (!route) {
|
||||
continue;
|
||||
}
|
||||
|
||||
update (client, Node::strip_desc, { strip_n }, { strip->name () });
|
||||
update (client, Node::strip_gain, { strip_n }, { strips ().strip_gain (strip_n) });
|
||||
update (client, Node::strip_pan, { strip_n }, { strips ().strip_pan (strip_n) });
|
||||
update (client, Node::strip_mute, { strip_n }, { strips ().strip_mute (strip_n) });
|
||||
|
||||
for (uint32_t plugin_n = 0 ; ; ++plugin_n) {
|
||||
boost::shared_ptr<PluginInsert> insert = strips ()
|
||||
.strip_plugin_insert (strip_n, plugin_n);
|
||||
if (!insert) {
|
||||
break;
|
||||
}
|
||||
|
||||
boost::shared_ptr<Plugin> plugin = insert->plugin ();
|
||||
update (client, Node::strip_plugin_desc, { strip_n, plugin_n },
|
||||
{ static_cast<std::string>(plugin->name ()) });
|
||||
|
||||
update (client, Node::strip_plugin_enable, { strip_n, plugin_n },
|
||||
{ strips ().strip_plugin_enabled (strip_n, plugin_n) });
|
||||
|
||||
for (uint32_t param_n = 0; param_n < plugin->parameter_count (); ++param_n) {
|
||||
boost::shared_ptr<AutomationControl> a_ctrl =
|
||||
strips ().strip_plugin_param_control (strip_n, plugin_n, param_n);
|
||||
if (!a_ctrl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// possible flags: enumeration, integer_step, logarithmic, sr_dependent, toggled
|
||||
ParameterDescriptor pd = a_ctrl->desc ();
|
||||
|
||||
if (pd.toggled) {
|
||||
update (client, Node::strip_plugin_param_desc, { strip_n, plugin_n, param_n },
|
||||
{ a_ctrl->name (), std::string("b") });
|
||||
} else if (pd.enumeration || pd.integer_step) {
|
||||
update (client, Node::strip_plugin_param_desc, { strip_n, plugin_n, param_n },
|
||||
{ a_ctrl->name (), std::string("i"), pd.lower, pd.upper, pd.integer_step });
|
||||
} else {
|
||||
update (client, Node::strip_plugin_param_desc, { strip_n, plugin_n, param_n },
|
||||
{ a_ctrl->name (), std::string("d"), pd.lower, pd.upper, pd.logarithmic });
|
||||
}
|
||||
|
||||
TypedValue value = strips ().strip_plugin_param_value (strip_n, plugin_n, param_n);
|
||||
update (client, Node::strip_plugin_param_value, { strip_n, plugin_n, param_n },
|
||||
{ value });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::tempo_handler (Client client, const NodeStateMessage& msg)
|
||||
{
|
||||
if (msg.is_write ()) {
|
||||
globals ().set_tempo (msg.state ().nth_val (0));
|
||||
} else {
|
||||
update (client, Node::tempo, {}, { globals ().tempo () });
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::strip_gain_handler (Client client, const NodeStateMessage& msg)
|
||||
{
|
||||
uint32_t strip_id = msg.state ().nth_addr (0);
|
||||
|
||||
if (msg.is_write ()) {
|
||||
strips ().set_strip_gain (strip_id, msg.state ().nth_val (0));
|
||||
} else {
|
||||
update (client, Node::strip_gain, { strip_id }, { strips ().strip_gain (strip_id) });
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::strip_pan_handler (Client client, const NodeStateMessage& msg)
|
||||
{
|
||||
uint32_t strip_id = msg.state ().nth_addr (0);
|
||||
|
||||
if (msg.is_write ()) {
|
||||
strips ().set_strip_pan (strip_id, msg.state ().nth_val (0));
|
||||
} else {
|
||||
update (client, Node::strip_pan, { strip_id }, { strips ().strip_pan(strip_id) });
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::strip_mute_handler (Client client, const NodeStateMessage& msg)
|
||||
{
|
||||
uint32_t strip_id = msg.state ().nth_addr (0);
|
||||
|
||||
if (msg.is_write ()) {
|
||||
strips ().set_strip_mute (strip_id, msg.state ().nth_val (0));
|
||||
} else {
|
||||
update (client, Node::strip_mute, { strip_id }, { strips ().strip_mute (strip_id) });
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::strip_plugin_enable_handler (Client client, const NodeStateMessage& msg)
|
||||
{
|
||||
uint32_t strip_id = msg.state ().nth_addr (0);
|
||||
uint32_t plugin_id = msg.state ().nth_addr (1);
|
||||
|
||||
if (msg.is_write ()) {
|
||||
strips ().set_strip_plugin_enabled (strip_id, plugin_id, msg.state ().nth_val (0));
|
||||
} else {
|
||||
update (client, Node::strip_plugin_enable, { strip_id, plugin_id },
|
||||
{ strips ().strip_plugin_enabled (strip_id, plugin_id) });
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::strip_plugin_param_value_handler (Client client, const NodeStateMessage& msg)
|
||||
{
|
||||
uint32_t strip_id = msg.state ().nth_addr (0);
|
||||
uint32_t plugin_id = msg.state ().nth_addr (1);
|
||||
uint32_t param_id = msg.state ().nth_addr (2);
|
||||
|
||||
if (msg.is_write ()) {
|
||||
strips ().set_strip_plugin_param_value (strip_id, plugin_id, param_id,
|
||||
msg.state ().nth_val (0));
|
||||
} else {
|
||||
TypedValue value = strips ().strip_plugin_param_value (strip_id, plugin_id, param_id);
|
||||
update (client, Node::strip_plugin_param_value, { strip_id, plugin_id, param_id },
|
||||
{ value });
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsDispatcher::update (Client client, std::string node,
|
||||
std::initializer_list<uint32_t> addr, std::initializer_list<TypedValue> val)
|
||||
{
|
||||
server ().update_client (client, { node, addr, val }, true);
|
||||
}
|
||||
58
libs/surfaces/websockets/dispatcher.h
Normal file
58
libs/surfaces/websockets/dispatcher.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef websockets_dispatcher_h
|
||||
#define websockets_dispatcher_h
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "component.h"
|
||||
#include "client.h"
|
||||
#include "message.h"
|
||||
|
||||
class WebsocketsDispatcher : public SurfaceComponent
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
WebsocketsDispatcher (ArdourSurface::ArdourWebsockets& surface) : SurfaceComponent (surface) {};
|
||||
virtual ~WebsocketsDispatcher () {};
|
||||
|
||||
void dispatch (Client, const NodeStateMessage&);
|
||||
void update_all_nodes (Client);
|
||||
|
||||
private:
|
||||
|
||||
typedef void (WebsocketsDispatcher::*DispatcherMethod) (Client, const NodeStateMessage&);
|
||||
typedef std::unordered_map<std::string, DispatcherMethod> NodeMethodMap;
|
||||
|
||||
static NodeMethodMap _node_to_method;
|
||||
|
||||
void tempo_handler (Client, const NodeStateMessage&);
|
||||
void strip_gain_handler (Client, const NodeStateMessage&);
|
||||
void strip_pan_handler (Client, const NodeStateMessage&);
|
||||
void strip_mute_handler (Client, const NodeStateMessage&);
|
||||
void strip_plugin_enable_handler (Client, const NodeStateMessage&);
|
||||
void strip_plugin_param_value_handler (Client, const NodeStateMessage&);
|
||||
|
||||
void update (Client, std::string, std::initializer_list<uint32_t>,
|
||||
std::initializer_list<TypedValue>);
|
||||
|
||||
};
|
||||
|
||||
#endif // websockets_dispatcher_h
|
||||
171
libs/surfaces/websockets/feedback.cc
Normal file
171
libs/surfaces/websockets/feedback.cc
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 "ardour/session.h"
|
||||
#include "ardour/tempo.h"
|
||||
#include "ardour/plugin_insert.h"
|
||||
#include "ardour/meter.h"
|
||||
|
||||
#include "feedback.h"
|
||||
#include "strips.h"
|
||||
#include "globals.h"
|
||||
#include "state.h"
|
||||
#include "server.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
typedef boost::function<void ()> SignalObserver;
|
||||
|
||||
int
|
||||
ArdourFeedback::start ()
|
||||
{
|
||||
observe_globals ();
|
||||
observe_strips ();
|
||||
|
||||
// some things need polling like the strip meters
|
||||
Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (100); // ms
|
||||
_periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this,
|
||||
&ArdourFeedback::poll));
|
||||
periodic_timeout->attach (main_loop ()->get_context ());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ArdourFeedback::stop ()
|
||||
{
|
||||
_periodic_connection.disconnect ();
|
||||
_signal_connections.drop_connections ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ArdourFeedback::poll () const
|
||||
{
|
||||
for (uint32_t strip_n = 0; strip_n < strips ().strip_count (); ++strip_n) {
|
||||
// meters
|
||||
boost::shared_ptr<Stripable> strip = strips ().nth_strip (strip_n);
|
||||
boost::shared_ptr<PeakMeter> meter = strip->peak_meter ();
|
||||
float db = meter ? meter->meter_level (0, MeterMCP) : -193;
|
||||
update_all (Node::strip_meter, { strip_n }, static_cast<double>(db));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ArdourFeedback::observe_globals ()
|
||||
{
|
||||
// tempo
|
||||
SignalObserver observer = [this] () {
|
||||
update_all (Node::tempo, {}, globals ().tempo ());
|
||||
};
|
||||
|
||||
session ().tempo_map ().PropertyChanged.connect (_signal_connections, MISSING_INVALIDATOR,
|
||||
boost::bind<void> (observer), event_loop ());
|
||||
}
|
||||
|
||||
void
|
||||
ArdourFeedback::observe_strips ()
|
||||
{
|
||||
for (uint32_t strip_n = 0; strip_n < strips ().strip_count (); ++strip_n) {
|
||||
boost::shared_ptr<Stripable> strip = strips ().nth_strip (strip_n);
|
||||
|
||||
// gain
|
||||
SignalObserver observer = [this, strip_n] () {
|
||||
// fires multiple times (4x as of ardour 6.0)
|
||||
update_all (Node::strip_gain, { strip_n }, strips ().strip_gain (strip_n));
|
||||
};
|
||||
strip->gain_control ()->Changed.connect (_signal_connections, MISSING_INVALIDATOR,
|
||||
boost::bind<void> (observer), event_loop ());
|
||||
|
||||
// pan
|
||||
observer = [this, strip_n] () {
|
||||
update_all (Node::strip_pan, { strip_n }, strips ().strip_pan (strip_n));
|
||||
};
|
||||
strip->pan_azimuth_control ()->Changed.connect (_signal_connections, MISSING_INVALIDATOR,
|
||||
boost::bind<void> (observer), event_loop ());
|
||||
|
||||
// mute
|
||||
observer = [this, strip_n] () {
|
||||
update_all (Node::strip_mute, { strip_n }, strips ().strip_mute (strip_n));
|
||||
};
|
||||
strip->mute_control ()->Changed.connect (_signal_connections, MISSING_INVALIDATOR,
|
||||
boost::bind<void> (observer), event_loop ());
|
||||
|
||||
observe_strip_plugins (strip_n, strip);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArdourFeedback::observe_strip_plugins (uint32_t strip_n, boost::shared_ptr<ARDOUR::Stripable> strip)
|
||||
{
|
||||
for (uint32_t plugin_n = 0 ; ; ++plugin_n) {
|
||||
boost::shared_ptr<PluginInsert> insert = strips ().strip_plugin_insert (strip_n, plugin_n);
|
||||
if (!insert) {
|
||||
break;
|
||||
}
|
||||
|
||||
SignalObserver observer = [this, strip_n, plugin_n] () {
|
||||
update_all (Node::strip_plugin_enable, { strip_n, plugin_n },
|
||||
strips ().strip_plugin_enabled (strip_n, plugin_n));
|
||||
};
|
||||
|
||||
uint32_t bypass = insert->plugin ()->designated_bypass_port ();
|
||||
Evoral::Parameter param = Evoral::Parameter (PluginAutomation, 0, bypass);
|
||||
boost::shared_ptr<AutomationControl> control = insert->automation_control (param);
|
||||
|
||||
if (control) {
|
||||
control->Changed.connect (_signal_connections, MISSING_INVALIDATOR,
|
||||
boost::bind<void> (observer), event_loop ());
|
||||
}
|
||||
|
||||
observe_strip_plugin_param_values (strip_n, plugin_n, insert);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArdourFeedback::observe_strip_plugin_param_values (uint32_t strip_n,
|
||||
uint32_t plugin_n, boost::shared_ptr<ARDOUR::PluginInsert> insert)
|
||||
{
|
||||
boost::shared_ptr<Plugin> plugin = insert->plugin ();
|
||||
|
||||
for (uint32_t param_n = 0; param_n < plugin->parameter_count (); ++param_n) {
|
||||
boost::shared_ptr<AutomationControl> control = strips ().strip_plugin_param_control (
|
||||
strip_n, plugin_n, param_n);
|
||||
|
||||
if (!control) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalObserver observer = [this, control, strip_n, plugin_n, param_n] () {
|
||||
update_all (Node::strip_plugin_param_value, { strip_n, plugin_n, param_n },
|
||||
ArdourStrips::plugin_param_value (control));
|
||||
};
|
||||
|
||||
control->Changed.connect (_signal_connections, MISSING_INVALIDATOR,
|
||||
boost::bind<void> (observer), event_loop ());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArdourFeedback::update_all (std::string node, std::initializer_list<uint32_t> addr,
|
||||
TypedValue val) const
|
||||
{
|
||||
server ().update_all_clients ({ node, addr, { val }}, false);
|
||||
}
|
||||
56
libs/surfaces/websockets/feedback.h
Normal file
56
libs/surfaces/websockets/feedback.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ardour_feedback_h
|
||||
#define ardour_feedback_h
|
||||
|
||||
#include <glibmm/main.h>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "component.h"
|
||||
#include "typed_value.h"
|
||||
|
||||
class ArdourFeedback : public SurfaceComponent
|
||||
{
|
||||
public:
|
||||
|
||||
ArdourFeedback (ArdourSurface::ArdourWebsockets& surface) : SurfaceComponent (surface) {};
|
||||
virtual ~ArdourFeedback () {};
|
||||
|
||||
int start ();
|
||||
int stop ();
|
||||
|
||||
private:
|
||||
|
||||
Glib::Threads::Mutex _client_state_lock;
|
||||
PBD::ScopedConnectionList _signal_connections;
|
||||
sigc::connection _periodic_connection;
|
||||
|
||||
bool poll () const;
|
||||
|
||||
void observe_globals ();
|
||||
void observe_strips ();
|
||||
void observe_strip_plugins (uint32_t, boost::shared_ptr<ARDOUR::Stripable>);
|
||||
void observe_strip_plugin_param_values (uint32_t, uint32_t,
|
||||
boost::shared_ptr<ARDOUR::PluginInsert>);
|
||||
|
||||
void update_all (std::string, std::initializer_list<uint32_t>, TypedValue) const;
|
||||
|
||||
};
|
||||
|
||||
#endif // ardour_feedback_h
|
||||
39
libs/surfaces/websockets/globals.cc
Normal file
39
libs/surfaces/websockets/globals.cc
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 "ardour/tempo.h"
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
double
|
||||
ArdourGlobals::tempo () const
|
||||
{
|
||||
Tempo tempo = session ().tempo_map ().tempo_at_sample (0);
|
||||
return tempo.note_type () * tempo.pulses_per_minute ();
|
||||
}
|
||||
|
||||
void
|
||||
ArdourGlobals::set_tempo (double bpm)
|
||||
{
|
||||
bpm = max (0.01, bpm);
|
||||
TempoMap& tempo_map = session ().tempo_map ();
|
||||
Tempo tempo (bpm, tempo_map.tempo_at_sample (0).note_type (), bpm);
|
||||
tempo_map.add_tempo (tempo, 0.0, 0, AudioTime);
|
||||
}
|
||||
36
libs/surfaces/websockets/globals.h
Normal file
36
libs/surfaces/websockets/globals.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ardour_globals_h
|
||||
#define ardour_globals_h
|
||||
|
||||
#include "component.h"
|
||||
|
||||
class ArdourGlobals : public SurfaceComponent
|
||||
{
|
||||
public:
|
||||
|
||||
ArdourGlobals (ArdourSurface::ArdourWebsockets& surface) : SurfaceComponent (surface) {};
|
||||
virtual ~ArdourGlobals () {};
|
||||
|
||||
double tempo () const;
|
||||
void set_tempo (double);
|
||||
|
||||
};
|
||||
|
||||
#endif // ardour_globals_h
|
||||
73
libs/surfaces/websockets/interface.cc
Normal file
73
libs/surfaces/websockets/interface.cc
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2006-2015 Paul Davis <paul@linuxaudiosystems.com>
|
||||
* Copyright (C) 2009 David Robillard <d@drobilla.net>
|
||||
*
|
||||
* 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 "ardour/rc_configuration.h"
|
||||
#include "control_protocol/control_protocol.h"
|
||||
|
||||
#include "ardour_websockets.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
using namespace ArdourSurface;
|
||||
|
||||
static ControlProtocol*
|
||||
new_ardour_websockets_protocol (ControlProtocolDescriptor* /*descriptor*/,
|
||||
Session* s)
|
||||
{
|
||||
ArdourWebsockets* surface = new ArdourWebsockets (*s);
|
||||
|
||||
surface->set_active (true);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
static void
|
||||
delete_ardour_websockets_protocol (ControlProtocolDescriptor* /*descriptor*/,
|
||||
ControlProtocol* cp)
|
||||
{
|
||||
delete cp;
|
||||
}
|
||||
|
||||
static bool
|
||||
probe_ardour_websockets_protocol (ControlProtocolDescriptor* /*descriptor*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void*
|
||||
ardour_websockets_request_buffer_factory (uint32_t num_requests)
|
||||
{
|
||||
return ArdourWebsockets::request_factory (num_requests);
|
||||
}
|
||||
|
||||
static ControlProtocolDescriptor ardour_websockets_descriptor = {
|
||||
/*name : */ SURFACE_NAME,
|
||||
/*id : */ SURFACE_ID,
|
||||
/*ptr : */ 0,
|
||||
/*module : */ 0,
|
||||
/*mandatory : */ 0,
|
||||
/*supports_feedback : */ true,
|
||||
/*probe : */ probe_ardour_websockets_protocol,
|
||||
/*initialize : */ new_ardour_websockets_protocol,
|
||||
/*destroy : */ delete_ardour_websockets_protocol,
|
||||
/*request_buffer_factory */ ardour_websockets_request_buffer_factory
|
||||
};
|
||||
|
||||
extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () {
|
||||
return &ardour_websockets_descriptor;
|
||||
}
|
||||
193
libs/surfaces/websockets/message.cc
Normal file
193
libs/surfaces/websockets/message.cc
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include "message.h"
|
||||
|
||||
// JSON does not support Infinity or NaN
|
||||
#define XSTR(s) STR(s)
|
||||
#define STR(s) #s
|
||||
#define JSON_INF 1.0e+128
|
||||
#define JSON_INF_STR XSTR(JSON_INF)
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
NodeStateMessage::NodeStateMessage (const NodeState& state)
|
||||
: _valid (true)
|
||||
, _state (state)
|
||||
{
|
||||
_write = state.n_val () > 0;
|
||||
}
|
||||
|
||||
NodeStateMessage::NodeStateMessage (void *buf, size_t len)
|
||||
: _valid (false)
|
||||
, _write (false)
|
||||
{
|
||||
try {
|
||||
std::string s { static_cast<char *>(buf), len };
|
||||
|
||||
std::istringstream is { s };
|
||||
pt::ptree root;
|
||||
pt::read_json (is, root);
|
||||
|
||||
_state = NodeState { root.get<std::string> ("node") };
|
||||
|
||||
pt::ptree addr = root.get_child ("addr", pt::ptree ());
|
||||
|
||||
for (pt::ptree::iterator it = addr.begin (); it != addr.end (); ++it) {
|
||||
// throws if datatype not uint32_t
|
||||
_state.add_addr (boost::lexical_cast<uint32_t>(it->second.data ()));
|
||||
}
|
||||
|
||||
pt::ptree val = root.get_child ("val", pt::ptree ());
|
||||
|
||||
for (pt::ptree::iterator it = val.begin (); it != val.end (); ++it) {
|
||||
|
||||
std::string val = it->second.data ();
|
||||
|
||||
try {
|
||||
_state.add_val (boost::lexical_cast<int>(val));
|
||||
} catch (const boost::bad_lexical_cast&) {
|
||||
try {
|
||||
double d = boost::lexical_cast<double>(val);
|
||||
if (d >= JSON_INF) {
|
||||
d = std::numeric_limits<double>::infinity ();
|
||||
} else if (d <= -JSON_INF) {
|
||||
d = -std::numeric_limits<double>::infinity ();
|
||||
}
|
||||
_state.add_val (d);
|
||||
} catch (const boost::bad_lexical_cast&) {
|
||||
if (val == "false") {
|
||||
_state.add_val (false);
|
||||
} else if (val == "true") {
|
||||
_state.add_val (true);
|
||||
} else {
|
||||
_state.add_val (val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_state.n_val () > 0) {
|
||||
_write = true;
|
||||
}
|
||||
|
||||
_valid = true;
|
||||
|
||||
} catch (const std::exception& exc) {
|
||||
#ifdef DEBUG
|
||||
std::cerr << "cannot parse message - " << exc.what () << std::endl;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
NodeStateMessage::serialize (void *buf, size_t len) const
|
||||
{
|
||||
// boost json writes all values as strings, we do not want that
|
||||
|
||||
if (len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "{\"node\":\"" << _state.node () << "\"";
|
||||
|
||||
int n_addr = _state.n_addr ();
|
||||
|
||||
if (n_addr > 0) {
|
||||
ss << ",\"addr\":[";
|
||||
|
||||
for (int i = 0; i < n_addr; i++) {
|
||||
if (i > 0) {
|
||||
ss << ',';
|
||||
}
|
||||
|
||||
ss << _state.nth_addr (i);
|
||||
}
|
||||
|
||||
ss << "]";
|
||||
}
|
||||
|
||||
int n_val = _state.n_val ();
|
||||
|
||||
if (n_val > 0) {
|
||||
ss << ",\"val\":[";
|
||||
|
||||
for (int i = 0; i < n_val; i++) {
|
||||
if (i > 0) {
|
||||
ss << ',';
|
||||
}
|
||||
|
||||
TypedValue val = _state.nth_val (i);
|
||||
|
||||
switch (val.type ()) {
|
||||
case TypedValue::Empty:
|
||||
ss << "null";
|
||||
break;
|
||||
case TypedValue::Bool:
|
||||
ss << (static_cast<bool>(val) ? "true" : "false");
|
||||
break;
|
||||
case TypedValue::Int:
|
||||
ss << static_cast<int>(val);
|
||||
break;
|
||||
case TypedValue::Double: {
|
||||
double d = static_cast<double>(val);
|
||||
if (d == std::numeric_limits<double>::infinity ()) {
|
||||
ss << JSON_INF_STR;
|
||||
} else if (d == -std::numeric_limits<double>::infinity ()) {
|
||||
ss << "-" JSON_INF_STR;
|
||||
} else {
|
||||
ss << d;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TypedValue::String:
|
||||
ss << '"' << static_cast<std::string>(val) << '"';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ss << "]";
|
||||
}
|
||||
|
||||
ss << '}';
|
||||
|
||||
std::string s = ss.str ();
|
||||
const char *cs = s.c_str ();
|
||||
size_t cs_sz = strlen (cs);
|
||||
|
||||
if (len < cs_sz) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy (buf, cs, cs_sz);
|
||||
|
||||
return cs_sz;
|
||||
}
|
||||
45
libs/surfaces/websockets/message.h
Normal file
45
libs/surfaces/websockets/message.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef websockets_message_h
|
||||
#define websockets_message_h
|
||||
|
||||
#include "state.h"
|
||||
|
||||
class NodeStateMessage
|
||||
{
|
||||
public:
|
||||
|
||||
NodeStateMessage (const NodeState& state);
|
||||
NodeStateMessage (void *, size_t);
|
||||
|
||||
size_t serialize (void *, size_t) const;
|
||||
|
||||
bool is_valid () const { return _valid; }
|
||||
bool is_write () const { return _write; }
|
||||
const NodeState& state () const { return _state; }
|
||||
|
||||
private:
|
||||
|
||||
bool _valid;
|
||||
bool _write;
|
||||
NodeState _state;
|
||||
|
||||
};
|
||||
|
||||
#endif // websockets_message_h
|
||||
383
libs/surfaces/websockets/server.cc
Normal file
383
libs/surfaces/websockets/server.cc
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include "server.h"
|
||||
#include "dispatcher.h"
|
||||
|
||||
using namespace Glib;
|
||||
|
||||
WebsocketsServer::WebsocketsServer
|
||||
(ArdourSurface::ArdourWebsockets& surface)
|
||||
: SurfaceComponent (surface)
|
||||
, _lws_context (0)
|
||||
{
|
||||
// keep references to all config for libwebsockets 2
|
||||
_lws_proto[0] = {
|
||||
"lws-ardour", // name
|
||||
WebsocketsServer::lws_callback, // callback
|
||||
0, // per_session_data_size
|
||||
0, // rx_buffer_size
|
||||
0, // id
|
||||
0, // user
|
||||
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
||||
0 // tx_packet_size
|
||||
#endif
|
||||
};
|
||||
_lws_proto[1] = {}; // sentinel
|
||||
|
||||
_lws_info = {};
|
||||
_lws_info.port = WEBSOCKET_LISTEN_PORT;
|
||||
_lws_info.protocols = _lws_proto;
|
||||
_lws_info.uid = -1;
|
||||
_lws_info.gid = -1;
|
||||
_lws_info.user = this;
|
||||
}
|
||||
|
||||
int
|
||||
WebsocketsServer::start ()
|
||||
{
|
||||
_lws_context = lws_create_context (&_lws_info);
|
||||
|
||||
if (!_lws_context) {
|
||||
PBD::error << "ArdourWebsockets: could not create libwebsockets context" << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// add_poll_fd() should have been called once during lws_create_context()
|
||||
// if _fd_ctx is empty then LWS_CALLBACK_ADD_POLL_FD was not called
|
||||
// this means libwesockets was not compiled with LWS_WITH_EXTERNAL_POLL
|
||||
// - macos homebrew libwebsockets: disabled (3.2.2 as of Feb 2020)
|
||||
// - linux ubuntu libwebsockets-dev: enabled (2.0.3 as of Feb 2020) but
|
||||
// #if defined(LWS_WITH_EXTERNAL_POLL) check is not reliable -- constant
|
||||
// missing from /usr/include/lws_config.h
|
||||
|
||||
if (_fd_ctx.empty ()) {
|
||||
PBD::error << "ArdourWebsockets: check your libwebsockets was compiled"
|
||||
" with LWS_WITH_EXTERNAL_POLL enabled" << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
WebsocketsServer::stop ()
|
||||
{
|
||||
for (LwsPollFdGlibSourceMap::iterator it = _fd_ctx.begin (); it != _fd_ctx.end (); ++it) {
|
||||
it->second.rg_iosrc->destroy ();
|
||||
|
||||
if (it->second.wg_iosrc) {
|
||||
it->second.wg_iosrc->destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
_fd_ctx.clear ();
|
||||
|
||||
if (_lws_context) {
|
||||
lws_context_destroy (_lws_context);
|
||||
_lws_context = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::update_client (Client wsi, const NodeState& state, bool force)
|
||||
{
|
||||
ClientContextMap::iterator it = _client_ctx.find (wsi);
|
||||
if (it == _client_ctx.end ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || !it->second.has_state (state)) {
|
||||
// write to client only if state was updated
|
||||
it->second.update_state (state);
|
||||
it->second.output_buf ().push_back (NodeStateMessage { state });
|
||||
lws_callback_on_writable (wsi);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::update_all_clients (const NodeState& state, bool force)
|
||||
{
|
||||
for (ClientContextMap::iterator it = _client_ctx.begin (); it != _client_ctx.end (); ++it) {
|
||||
update_client (it->second.wsi (), state, force);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::add_poll_fd (struct lws_pollargs *pa)
|
||||
{
|
||||
// fd can be SOCKET or int depending platform
|
||||
lws_sockfd_type fd = pa->fd;
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
RefPtr<IOChannel> g_channel = IOChannel::create_from_win32_socket (fd);
|
||||
#else
|
||||
RefPtr<IOChannel> g_channel = IOChannel::create_from_fd (fd);
|
||||
#endif
|
||||
RefPtr<IOSource> rg_iosrc { IOSource::create (g_channel, events_to_ioc (pa->events)) };
|
||||
rg_iosrc->connect (sigc::bind (sigc::mem_fun (*this, &WebsocketsServer::io_handler), fd));
|
||||
rg_iosrc->attach (main_loop ()->get_context ());
|
||||
|
||||
struct lws_pollfd lws_pfd;
|
||||
lws_pfd.fd = pa->fd;
|
||||
lws_pfd.events = pa->events;
|
||||
lws_pfd.revents = 0;
|
||||
_fd_ctx[fd] = LwsPollFdGlibSource { lws_pfd, g_channel, rg_iosrc, { } };
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::mod_poll_fd (struct lws_pollargs *pa)
|
||||
{
|
||||
LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd);
|
||||
if (it == _fd_ctx.end ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
it->second.lws_pfd.events = pa->events;
|
||||
|
||||
if (pa->events & POLLOUT) {
|
||||
// libwebsockets wants to write but cannot find a way to update
|
||||
// an existing glib::iosource event flags using glibmm,
|
||||
// create another iosource and set to IO_OUT, it will be destroyed
|
||||
// after clearing POLLOUT (see 'else' body below)
|
||||
|
||||
if (it->second.wg_iosrc) {
|
||||
// already polling for write
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<IOSource> wg_iosrc = it->second.g_channel->create_watch (IOCondition::IO_OUT);
|
||||
wg_iosrc->connect (sigc::bind (sigc::mem_fun (*this, &WebsocketsServer::io_handler), pa->fd));
|
||||
wg_iosrc->attach (main_loop ()->get_context ());
|
||||
it->second.wg_iosrc = wg_iosrc;
|
||||
} else {
|
||||
if (it->second.wg_iosrc) {
|
||||
it->second.wg_iosrc->destroy ();
|
||||
it->second.wg_iosrc = { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::del_poll_fd (struct lws_pollargs *pa)
|
||||
{
|
||||
LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd);
|
||||
if (it == _fd_ctx.end ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
it->second.rg_iosrc->destroy ();
|
||||
|
||||
if (it->second.wg_iosrc) {
|
||||
it->second.wg_iosrc->destroy ();
|
||||
}
|
||||
|
||||
_fd_ctx.erase (it);
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::add_client (Client wsi)
|
||||
{
|
||||
_client_ctx.emplace (wsi, ClientContext { wsi });
|
||||
dispatcher ().update_all_nodes (wsi); // send all state
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::del_client (Client wsi)
|
||||
{
|
||||
ClientContextMap::iterator it = _client_ctx.find (wsi);
|
||||
if (it != _client_ctx.end ()) {
|
||||
_client_ctx.erase (it);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::recv_client (Client wsi, void *buf, size_t len)
|
||||
{
|
||||
NodeStateMessage msg { buf, len };
|
||||
if (!msg.is_valid ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
std::cerr << "RX " << msg.state ().debug_str () << std::endl;
|
||||
#endif
|
||||
|
||||
ClientContextMap::iterator it = _client_ctx.find (wsi);
|
||||
if (it == _client_ctx.end ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// avoid echo
|
||||
it->second.update_state (msg.state ());
|
||||
|
||||
dispatcher ().dispatch (wsi, msg);
|
||||
}
|
||||
|
||||
void
|
||||
WebsocketsServer::write_client (Client wsi)
|
||||
{
|
||||
ClientContextMap::iterator it = _client_ctx.find (wsi);
|
||||
if (it == _client_ctx.end ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientOutputBuffer& pending = it->second.output_buf ();
|
||||
if (pending.empty ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// one lws_write() call per LWS_CALLBACK_SERVER_WRITEABLE callback
|
||||
|
||||
NodeStateMessage msg = pending.front ();
|
||||
pending.pop_front ();
|
||||
|
||||
unsigned char out_buf[1024];
|
||||
size_t len = msg.serialize (out_buf + LWS_PRE, 1024 - LWS_PRE);
|
||||
|
||||
if (len > 0) {
|
||||
#ifdef DEBUG
|
||||
std::cerr << "TX " << msg.state ().debug_str () << std::endl;
|
||||
#endif
|
||||
lws_write (wsi, out_buf + LWS_PRE, len, LWS_WRITE_TEXT);
|
||||
} else {
|
||||
PBD::error << "ArdourWebsockets: cannot serialize message" << endmsg;
|
||||
}
|
||||
|
||||
if (!pending.empty ()) {
|
||||
lws_callback_on_writable (wsi);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
WebsocketsServer::io_handler (Glib::IOCondition ioc, lws_sockfd_type fd)
|
||||
{
|
||||
// IO_IN=1, IO_PRI=2, IO_ERR=8, IO_HUP=16
|
||||
//printf ("io_handler ioc = %d\n", ioc);
|
||||
|
||||
LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (fd);
|
||||
if (it == _fd_ctx.end ()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct lws_pollfd *lws_pfd = &it->second.lws_pfd;
|
||||
lws_pfd->revents = ioc_to_events (ioc);
|
||||
|
||||
if (lws_service_fd (_lws_context, lws_pfd) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ioc & (IO_IN | IO_OUT);
|
||||
}
|
||||
|
||||
IOCondition
|
||||
WebsocketsServer::events_to_ioc (int events)
|
||||
{
|
||||
IOCondition ioc = { };
|
||||
|
||||
if (events & POLLIN) {
|
||||
ioc |= IO_IN;
|
||||
}
|
||||
|
||||
if (events & POLLOUT) {
|
||||
ioc |= IO_OUT;
|
||||
}
|
||||
|
||||
if (events & POLLHUP) {
|
||||
ioc |= IO_HUP;
|
||||
}
|
||||
|
||||
if (events & POLLERR) {
|
||||
ioc |= IO_ERR;
|
||||
}
|
||||
|
||||
return ioc;
|
||||
}
|
||||
|
||||
int
|
||||
WebsocketsServer::ioc_to_events (IOCondition ioc)
|
||||
{
|
||||
int events = 0;
|
||||
|
||||
if (ioc & IO_IN) {
|
||||
events |= POLLIN;
|
||||
}
|
||||
|
||||
if (ioc & IO_OUT) {
|
||||
events |= POLLOUT;
|
||||
}
|
||||
|
||||
if (ioc & IO_HUP) {
|
||||
events |= POLLHUP;
|
||||
}
|
||||
|
||||
if (ioc & IO_ERR) {
|
||||
events |= POLLERR;
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
int
|
||||
WebsocketsServer::lws_callback(struct lws* wsi, enum lws_callback_reasons reason,
|
||||
void *user, void *in, size_t len)
|
||||
{
|
||||
void *ctx_userdata = lws_context_user (lws_get_context (wsi));
|
||||
WebsocketsServer *server = static_cast<WebsocketsServer *>(ctx_userdata);
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_ADD_POLL_FD:
|
||||
server->add_poll_fd (static_cast<struct lws_pollargs *>(in));
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
|
||||
server->mod_poll_fd (static_cast<struct lws_pollargs *>(in));
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_DEL_POLL_FD:
|
||||
server->del_poll_fd (static_cast<struct lws_pollargs *>(in));
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
server->add_client (wsi);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLOSED:
|
||||
server->del_client (wsi);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
server->recv_client (wsi, in, len);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
||||
server->write_client (wsi);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
95
libs/surfaces/websockets/server.h
Normal file
95
libs/surfaces/websockets/server.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef websockets_server_h
|
||||
#define websockets_server_h
|
||||
|
||||
#include <unordered_map>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#if LWS_LIBRARY_VERSION_MAJOR < 3
|
||||
// <libwebsockets.h> includes <uv.h> which in turn includes
|
||||
// /usr/include/asm-generic/param.h on linux which defines HZ
|
||||
// and conflicts with libs/ardour/ardour/parameter_descriptor.h
|
||||
#undef HZ
|
||||
#else
|
||||
// also libwebsockets >=3 already includes integration with the glib event loop
|
||||
// but ubuntu default repositories are stuck at version 2, hold until requiring
|
||||
// version 3 in order to keep things simpler for the end user
|
||||
#endif
|
||||
|
||||
#include "component.h"
|
||||
#include "client.h"
|
||||
#include "state.h"
|
||||
#include "message.h"
|
||||
|
||||
#define WEBSOCKET_LISTEN_PORT 9000
|
||||
|
||||
struct LwsPollFdGlibSource {
|
||||
struct lws_pollfd lws_pfd;
|
||||
Glib::RefPtr<Glib::IOChannel> g_channel;
|
||||
Glib::RefPtr<Glib::IOSource> rg_iosrc;
|
||||
Glib::RefPtr<Glib::IOSource> wg_iosrc;
|
||||
};
|
||||
|
||||
class WebsocketsServer : public SurfaceComponent
|
||||
{
|
||||
public:
|
||||
|
||||
WebsocketsServer (ArdourSurface::ArdourWebsockets&);
|
||||
virtual ~WebsocketsServer () {};
|
||||
|
||||
int start ();
|
||||
int stop ();
|
||||
|
||||
void update_client (Client, const NodeState&, bool);
|
||||
void update_all_clients (const NodeState&, bool);
|
||||
|
||||
private:
|
||||
|
||||
struct lws_protocols _lws_proto[2];
|
||||
struct lws_context_creation_info _lws_info;
|
||||
struct lws_context *_lws_context;
|
||||
|
||||
Glib::RefPtr<Glib::IOChannel> _channel;
|
||||
|
||||
typedef std::unordered_map<lws_sockfd_type, LwsPollFdGlibSource> LwsPollFdGlibSourceMap;
|
||||
LwsPollFdGlibSourceMap _fd_ctx;
|
||||
|
||||
typedef std::unordered_map<Client, ClientContext> ClientContextMap;
|
||||
ClientContextMap _client_ctx;
|
||||
|
||||
void add_poll_fd (struct lws_pollargs*);
|
||||
void mod_poll_fd (struct lws_pollargs*);
|
||||
void del_poll_fd (struct lws_pollargs*);
|
||||
|
||||
void add_client (Client);
|
||||
void del_client (Client);
|
||||
void recv_client (Client, void *buf, size_t len);
|
||||
void write_client (Client);
|
||||
|
||||
bool io_handler (Glib::IOCondition, lws_sockfd_type);
|
||||
|
||||
Glib::IOCondition events_to_ioc (int);
|
||||
int ioc_to_events (Glib::IOCondition);
|
||||
|
||||
static int lws_callback(struct lws*, enum lws_callback_reasons, void *, void *, size_t);
|
||||
|
||||
};
|
||||
|
||||
#endif // websockets_server_h
|
||||
118
libs/surfaces/websockets/state.cc
Normal file
118
libs/surfaces/websockets/state.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 <sstream>
|
||||
|
||||
#include "state.h"
|
||||
|
||||
NodeState::NodeState ()
|
||||
{
|
||||
update_node_addr_hash ();
|
||||
}
|
||||
|
||||
NodeState::NodeState (std::string node)
|
||||
: _node (node)
|
||||
{
|
||||
update_node_addr_hash ();
|
||||
}
|
||||
|
||||
NodeState::NodeState (std::string node, std::initializer_list<uint32_t> addr,
|
||||
std::initializer_list<TypedValue> val)
|
||||
: _node (node)
|
||||
, _addr (addr)
|
||||
, _val (val)
|
||||
{
|
||||
update_node_addr_hash ();
|
||||
}
|
||||
|
||||
std::string
|
||||
NodeState::debug_str () const
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "node = " << _node;
|
||||
|
||||
if (!_addr.empty ()) {
|
||||
s << std::endl << " addr = ";
|
||||
|
||||
for (std::vector<uint32_t>::const_iterator it = _addr.begin (); it != _addr.end (); ++it) {
|
||||
s << *it << ";";
|
||||
}
|
||||
}
|
||||
|
||||
for (std::vector<TypedValue>::const_iterator it = _val.begin (); it != _val.end (); ++it) {
|
||||
s << std::endl << " val " << it->debug_str ();
|
||||
}
|
||||
|
||||
s << std::endl << " hash = " << _node_addr_hash;
|
||||
|
||||
return s.str ();
|
||||
}
|
||||
|
||||
int
|
||||
NodeState::n_addr () const
|
||||
{
|
||||
return static_cast<int>(_addr.size ());
|
||||
}
|
||||
|
||||
uint32_t
|
||||
NodeState::nth_addr (int n) const
|
||||
{
|
||||
return _addr[n];
|
||||
}
|
||||
|
||||
void
|
||||
NodeState::add_addr (uint32_t addr)
|
||||
{
|
||||
_addr.push_back (addr);
|
||||
update_node_addr_hash ();
|
||||
}
|
||||
|
||||
int
|
||||
NodeState::n_val () const
|
||||
{
|
||||
return static_cast<int>(_val.size ());
|
||||
}
|
||||
|
||||
TypedValue
|
||||
NodeState::nth_val (int n) const
|
||||
{
|
||||
if (n_val () < n) {
|
||||
return TypedValue ();
|
||||
}
|
||||
|
||||
return _val[n];
|
||||
}
|
||||
|
||||
void
|
||||
NodeState::add_val (TypedValue val)
|
||||
{
|
||||
_val.push_back (val);
|
||||
}
|
||||
|
||||
void
|
||||
NodeState::update_node_addr_hash ()
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << _node;
|
||||
|
||||
for (std::vector<uint32_t>::iterator it = _addr.begin (); it != _addr.end (); ++it) {
|
||||
ss << "_" << *it;
|
||||
}
|
||||
|
||||
_node_addr_hash = ss.str ();
|
||||
}
|
||||
95
libs/surfaces/websockets/state.h
Normal file
95
libs/surfaces/websockets/state.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef node_state_h
|
||||
#define node_state_h
|
||||
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "typed_value.h"
|
||||
|
||||
namespace Node {
|
||||
const std::string tempo = "tempo";
|
||||
const std::string strip_desc = "strip_desc";
|
||||
const std::string strip_meter = "strip_meter";
|
||||
const std::string strip_gain = "strip_gain";
|
||||
const std::string strip_pan = "strip_pan";
|
||||
const std::string strip_mute = "strip_mute";
|
||||
const std::string strip_plugin_desc = "strip_plugin_desc";
|
||||
const std::string strip_plugin_enable = "strip_plugin_enable";
|
||||
const std::string strip_plugin_param_desc = "strip_plugin_param_desc";
|
||||
const std::string strip_plugin_param_value = "strip_plugin_param_value";
|
||||
};
|
||||
|
||||
class NodeState {
|
||||
|
||||
public:
|
||||
|
||||
NodeState ();
|
||||
NodeState (std::string);
|
||||
NodeState (std::string, std::initializer_list<uint32_t>,
|
||||
std::initializer_list<TypedValue> = {});
|
||||
|
||||
std::string debug_str () const;
|
||||
|
||||
std::string node () const { return _node; }
|
||||
|
||||
int n_addr () const;
|
||||
uint32_t nth_addr (int) const;
|
||||
void add_addr (uint32_t);
|
||||
|
||||
int n_val () const;
|
||||
TypedValue nth_val (int) const;
|
||||
void add_val (TypedValue);
|
||||
|
||||
private:
|
||||
|
||||
std::string _node;
|
||||
std::vector<uint32_t> _addr;
|
||||
std::vector<TypedValue> _val;
|
||||
std::string _node_addr_hash;
|
||||
|
||||
void update_node_addr_hash ();
|
||||
|
||||
friend struct std::hash<NodeState>;
|
||||
friend struct std::equal_to<NodeState>;
|
||||
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<NodeState> {
|
||||
size_t operator () (const NodeState &state) const {
|
||||
// std::hash<const char*> produces a hash of the value of the
|
||||
// pointer (the memory address), it does not examine the contents
|
||||
// of any character array.
|
||||
return std::hash<std::string>()(state._node_addr_hash);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct equal_to<NodeState> {
|
||||
bool operator() (const NodeState& lhs, const NodeState& rhs) const {
|
||||
return lhs._node_addr_hash == rhs._node_addr_hash;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // node_state_h
|
||||
227
libs/surfaces/websockets/strips.cc
Normal file
227
libs/surfaces/websockets/strips.cc
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 "ardour/session.h"
|
||||
#include "ardour/plugin_insert.h"
|
||||
#include "ardour/dB.h"
|
||||
#include "pbd/controllable.h"
|
||||
|
||||
#include "strips.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
int
|
||||
ArdourStrips::start ()
|
||||
{
|
||||
// take an indexed snapshot of current strips
|
||||
StripableList strips;
|
||||
session ().get_stripables (strips, PresentationInfo::AllStripables);
|
||||
|
||||
for (StripableList::iterator strip = strips.begin (); strip != strips.end (); ++strip) {
|
||||
_strips.push_back (*strip);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ArdourStrips::stop ()
|
||||
{
|
||||
_strips.clear ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
double
|
||||
ArdourStrips::to_db (double k)
|
||||
{
|
||||
if (k == 0) {
|
||||
return -std::numeric_limits<double>::infinity ();
|
||||
}
|
||||
|
||||
float db = accurate_coefficient_to_dB (static_cast<float>(k));
|
||||
|
||||
return static_cast<double>(db);
|
||||
}
|
||||
|
||||
double
|
||||
ArdourStrips::from_db (double db)
|
||||
{
|
||||
if (db < -192) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float k = dB_to_coefficient (static_cast<float>(db));
|
||||
|
||||
return static_cast<double>(k);
|
||||
}
|
||||
|
||||
double
|
||||
ArdourStrips::strip_gain (uint32_t strip_n) const
|
||||
{
|
||||
return to_db (nth_strip (strip_n)->gain_control ()->get_value ());
|
||||
}
|
||||
|
||||
void
|
||||
ArdourStrips::set_strip_gain (uint32_t strip_n, double db)
|
||||
{
|
||||
nth_strip (strip_n)->gain_control ()->set_value (from_db (db), PBD::Controllable::NoGroup);
|
||||
}
|
||||
|
||||
double
|
||||
ArdourStrips::strip_pan (uint32_t strip_n) const
|
||||
{
|
||||
// scale from [0.0 ; 1.0] to [-1.0 ; 1.0]
|
||||
return 2.0 * nth_strip (strip_n)->pan_azimuth_control ()->get_value () - 1.0;
|
||||
}
|
||||
|
||||
void
|
||||
ArdourStrips::set_strip_pan (uint32_t strip_n, double value)
|
||||
{
|
||||
value = (value + 1.0) / 2.0;
|
||||
nth_strip (strip_n)->pan_azimuth_control ()->set_value (value, PBD::Controllable::NoGroup);
|
||||
}
|
||||
|
||||
bool
|
||||
ArdourStrips::strip_mute (uint32_t strip_n) const
|
||||
{
|
||||
return nth_strip (strip_n)->mute_control ()->muted ();
|
||||
}
|
||||
|
||||
void
|
||||
ArdourStrips::set_strip_mute (uint32_t strip_n, bool mute)
|
||||
{
|
||||
nth_strip (strip_n)->mute_control ()->set_value (mute ? 1.0 : 0.0, PBD::Controllable::NoGroup);
|
||||
}
|
||||
|
||||
bool
|
||||
ArdourStrips::strip_plugin_enabled (uint32_t strip_n, uint32_t plugin_n) const
|
||||
{
|
||||
return strip_plugin_insert (strip_n, plugin_n)->enabled ();
|
||||
}
|
||||
|
||||
void
|
||||
ArdourStrips::set_strip_plugin_enabled (uint32_t strip_n, uint32_t plugin_n, bool enabled)
|
||||
{
|
||||
strip_plugin_insert (strip_n, plugin_n)->enable (enabled);
|
||||
}
|
||||
|
||||
TypedValue
|
||||
ArdourStrips::strip_plugin_param_value (uint32_t strip_n, uint32_t plugin_n,
|
||||
uint32_t param_n) const
|
||||
{
|
||||
return plugin_param_value (strip_plugin_param_control (strip_n, plugin_n, param_n));
|
||||
}
|
||||
|
||||
void
|
||||
ArdourStrips::set_strip_plugin_param_value (uint32_t strip_n, uint32_t plugin_n,
|
||||
uint32_t param_n, TypedValue value)
|
||||
{
|
||||
boost::shared_ptr<AutomationControl> control = strip_plugin_param_control (
|
||||
strip_n, plugin_n, param_n);
|
||||
|
||||
if (control) {
|
||||
ParameterDescriptor pd = control->desc ();
|
||||
double dbl_val;
|
||||
|
||||
if (pd.toggled) {
|
||||
dbl_val = static_cast<double>(static_cast<bool>(value));
|
||||
} else if (pd.enumeration || pd.integer_step) {
|
||||
dbl_val = static_cast<double>(static_cast<int>(value));
|
||||
} else {
|
||||
dbl_val = static_cast<double>(value);
|
||||
}
|
||||
|
||||
control->set_value (dbl_val, PBD::Controllable::NoGroup);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ArdourStrips::strip_count () const
|
||||
{
|
||||
return _strips.size ();
|
||||
}
|
||||
|
||||
boost::shared_ptr<Stripable>
|
||||
ArdourStrips::nth_strip (uint32_t strip_n) const
|
||||
{
|
||||
if (strip_n < _strips.size ()) {
|
||||
return _strips[strip_n];
|
||||
}
|
||||
|
||||
return boost::shared_ptr<Stripable>();
|
||||
}
|
||||
|
||||
TypedValue
|
||||
ArdourStrips::plugin_param_value (boost::shared_ptr<ARDOUR::AutomationControl> control)
|
||||
{
|
||||
TypedValue value = TypedValue ();
|
||||
|
||||
if (control) {
|
||||
ParameterDescriptor pd = control->desc ();
|
||||
|
||||
if (pd.toggled) {
|
||||
value = TypedValue (static_cast<bool>(control->get_value ()));
|
||||
} else if (pd.enumeration || pd.integer_step) {
|
||||
value = TypedValue (static_cast<int>(control->get_value ()));
|
||||
} else {
|
||||
value = TypedValue (control->get_value ());
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
boost::shared_ptr<PluginInsert>
|
||||
ArdourStrips::strip_plugin_insert (uint32_t strip_n, uint32_t plugin_n) const
|
||||
{
|
||||
boost::shared_ptr<Stripable> strip = nth_strip (strip_n);
|
||||
boost::shared_ptr<Route> route = boost::dynamic_pointer_cast<Route> (strip);
|
||||
boost::shared_ptr<Processor> processor = route->nth_plugin (plugin_n);
|
||||
|
||||
if (processor) {
|
||||
boost::shared_ptr<PluginInsert> insert =
|
||||
boost::static_pointer_cast<PluginInsert> (processor);
|
||||
|
||||
if (insert) {
|
||||
return insert;
|
||||
}
|
||||
}
|
||||
|
||||
return boost::shared_ptr<PluginInsert>();
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationControl>
|
||||
ArdourStrips::strip_plugin_param_control (uint32_t strip_n, uint32_t plugin_n,
|
||||
uint32_t param_n) const
|
||||
{
|
||||
boost::shared_ptr<PluginInsert> insert = strip_plugin_insert (strip_n, plugin_n);
|
||||
|
||||
if (insert) {
|
||||
bool ok = false;
|
||||
boost::shared_ptr<Plugin> plugin = insert->plugin ();
|
||||
uint32_t control_id = plugin->nth_parameter (param_n, ok);
|
||||
|
||||
if (ok && plugin->parameter_is_input (control_id)) {
|
||||
boost::shared_ptr<AutomationControl> control =
|
||||
insert->automation_control (Evoral::Parameter(PluginAutomation, 0, control_id));
|
||||
return control;
|
||||
}
|
||||
}
|
||||
|
||||
return boost::shared_ptr<AutomationControl>();
|
||||
}
|
||||
70
libs/surfaces/websockets/strips.h
Normal file
70
libs/surfaces/websockets/strips.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ardour_strips_h
|
||||
#define ardour_strips_h
|
||||
|
||||
#include "component.h"
|
||||
#include "typed_value.h"
|
||||
|
||||
class ArdourStrips : public SurfaceComponent
|
||||
{
|
||||
public:
|
||||
|
||||
ArdourStrips (ArdourSurface::ArdourWebsockets& surface) : SurfaceComponent (surface) {};
|
||||
virtual ~ArdourStrips () {};
|
||||
|
||||
int start ();
|
||||
int stop ();
|
||||
|
||||
static double to_db (double);
|
||||
static double from_db (double);
|
||||
|
||||
double strip_gain (uint32_t) const;
|
||||
void set_strip_gain (uint32_t, double);
|
||||
|
||||
double strip_pan (uint32_t) const;
|
||||
void set_strip_pan (uint32_t, double);
|
||||
|
||||
bool strip_mute (uint32_t) const;
|
||||
void set_strip_mute (uint32_t, bool);
|
||||
|
||||
bool strip_plugin_enabled (uint32_t, uint32_t) const;
|
||||
void set_strip_plugin_enabled (uint32_t, uint32_t, bool);
|
||||
|
||||
TypedValue strip_plugin_param_value (uint32_t, uint32_t, uint32_t) const;
|
||||
void set_strip_plugin_param_value (uint32_t, uint32_t, uint32_t, TypedValue);
|
||||
|
||||
uint32_t strip_count () const;
|
||||
boost::shared_ptr<ARDOUR::Stripable> nth_strip (uint32_t) const;
|
||||
|
||||
boost::shared_ptr<ARDOUR::PluginInsert> strip_plugin_insert (uint32_t, uint32_t) const;
|
||||
|
||||
boost::shared_ptr<ARDOUR::AutomationControl> strip_plugin_param_control(
|
||||
uint32_t, uint32_t, uint32_t) const;
|
||||
|
||||
static TypedValue plugin_param_value (boost::shared_ptr<ARDOUR::AutomationControl>);
|
||||
|
||||
private:
|
||||
|
||||
typedef std::vector<boost::shared_ptr<ARDOUR::Stripable>> StripableVector;
|
||||
StripableVector _strips;
|
||||
|
||||
};
|
||||
|
||||
#endif // ardour_strips_h
|
||||
152
libs/surfaces/websockets/typed_value.cc
Normal file
152
libs/surfaces/websockets/typed_value.cc
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "typed_value.h"
|
||||
|
||||
#define DBL_TOLERANCE 0.001
|
||||
|
||||
TypedValue::operator
|
||||
bool () const
|
||||
{
|
||||
switch (_type) {
|
||||
case Bool:
|
||||
return _b;
|
||||
case Int:
|
||||
return _i != 0;
|
||||
case Double:
|
||||
return _d != 0;
|
||||
case String:
|
||||
return _s == "true";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue::operator
|
||||
int () const
|
||||
{
|
||||
switch (_type) {
|
||||
case Int:
|
||||
return _i;
|
||||
case Bool:
|
||||
return _b ? 1 : 0;
|
||||
case Double:
|
||||
return static_cast<int>(_d);
|
||||
case String:
|
||||
try {
|
||||
return std::stoi (_s);
|
||||
} catch (const std::exception&) {
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue::operator
|
||||
double () const
|
||||
{
|
||||
switch (_type) {
|
||||
case Double:
|
||||
return _d;
|
||||
case Bool:
|
||||
return _b ? 1.f : 0;
|
||||
case Int:
|
||||
return static_cast<double>(_i);
|
||||
case String:
|
||||
try {
|
||||
return std::stod (_s);
|
||||
} catch (const std::exception&) {
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue::operator
|
||||
std::string () const
|
||||
{
|
||||
switch (_type) {
|
||||
case String:
|
||||
return _s;
|
||||
case Bool:
|
||||
return _b ? "true" : "false";
|
||||
case Int:
|
||||
return std::to_string (_i);
|
||||
case Double:
|
||||
return std::to_string (_d);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TypedValue::operator== (const TypedValue& other) const
|
||||
{
|
||||
if (_type != other._type) {
|
||||
// make an exception when comparing doubles and ints
|
||||
// for example browser json implementations will send
|
||||
// 1 instead of 1.0 removing any type hint
|
||||
if ((_type == Int) && (other._type == Double)) {
|
||||
return fabs (static_cast<double>(_i) - other._d) < DBL_TOLERANCE;
|
||||
} else if ((_type == Double) && (other._type == Int)) {
|
||||
return fabs (_d - static_cast<double>(other._i)) < DBL_TOLERANCE;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (_type) {
|
||||
case Bool:
|
||||
return _b == other._b;
|
||||
case Int:
|
||||
return _i == other._i;
|
||||
case Double: {
|
||||
double inf = std::numeric_limits<double>::infinity ();
|
||||
return ((_d == inf) && (other._d == inf))
|
||||
|| ((_d == -inf) && (other._d == -inf))
|
||||
|| (fabs (_d - other._d) < DBL_TOLERANCE);
|
||||
}
|
||||
case String:
|
||||
return _s == other._s;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TypedValue::operator!= (const TypedValue& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
std::string
|
||||
TypedValue::debug_str () const
|
||||
{
|
||||
char s[256];
|
||||
|
||||
sprintf(s, "type = %d; b = %d; i = %d; d = %f; s = \"%s\"",
|
||||
_type, _b, _i, _d, _s.c_str ());
|
||||
|
||||
return s;
|
||||
}
|
||||
65
libs/surfaces/websockets/typed_value.h
Normal file
65
libs/surfaces/websockets/typed_value.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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 <string>
|
||||
|
||||
#ifndef typed_value_h
|
||||
#define typed_value_h
|
||||
|
||||
class TypedValue
|
||||
{
|
||||
public:
|
||||
|
||||
enum Type {
|
||||
Empty,
|
||||
Bool,
|
||||
Int,
|
||||
Double,
|
||||
String
|
||||
};
|
||||
|
||||
TypedValue (): _type (Empty) { }
|
||||
TypedValue (bool value): _type { Bool }, _b (value) { }
|
||||
TypedValue (int value): _type { Int }, _i (value) { }
|
||||
TypedValue (double value): _type { Double }, _d (value) { }
|
||||
TypedValue (std::string value): _type { String }, _s (value) { }
|
||||
|
||||
bool empty () const { return _type == Empty; };
|
||||
Type type () const { return _type; };
|
||||
|
||||
operator bool () const;
|
||||
operator int () const;
|
||||
operator double () const;
|
||||
operator std::string () const;
|
||||
|
||||
bool operator== (const TypedValue& other) const;
|
||||
bool operator!= (const TypedValue& other) const;
|
||||
|
||||
std::string debug_str () const;
|
||||
|
||||
private:
|
||||
|
||||
Type _type;
|
||||
bool _b = false;
|
||||
int _i = 0;
|
||||
double _d = 0.0;
|
||||
std::string _s;
|
||||
|
||||
};
|
||||
|
||||
#endif // typed_value_h
|
||||
42
libs/surfaces/websockets/wscript
Normal file
42
libs/surfaces/websockets/wscript
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
from waflib.extras import autowaf as autowaf
|
||||
import os
|
||||
|
||||
# Mandatory variables
|
||||
top = '.'
|
||||
out = 'build'
|
||||
|
||||
def options(opt):
|
||||
autowaf.set_options(opt)
|
||||
|
||||
def configure(conf):
|
||||
autowaf.configure(conf)
|
||||
|
||||
def build(bld):
|
||||
obj = bld(features = 'cxx cxxshlib')
|
||||
obj.source = '''
|
||||
interface.cc
|
||||
ardour_websockets.cc
|
||||
typed_value.cc
|
||||
state.cc
|
||||
message.cc
|
||||
client.cc
|
||||
component.cc
|
||||
strips.cc
|
||||
globals.cc
|
||||
server.cc
|
||||
feedback.cc
|
||||
dispatcher.cc
|
||||
'''
|
||||
obj.export_includes = ['.']
|
||||
obj.defines = [ 'PACKAGE="ardour_websockets"' ]
|
||||
obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
|
||||
obj.includes = ['.', './websockets']
|
||||
obj.name = 'libardour_websockets'
|
||||
obj.target = 'ardour_websockets'
|
||||
obj.uselib = 'GLIBMM XML WEBSOCKETS OPENSSL'
|
||||
obj.use = 'libardour libardour_cp libgtkmm2ext libpbd'
|
||||
obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
|
||||
|
||||
def shutdown():
|
||||
autowaf.shutdown()
|
||||
@@ -76,6 +76,10 @@ def configure(conf):
|
||||
else:
|
||||
print('You are missing the cwiid headers needed to compile wiimote support')
|
||||
|
||||
autowaf.check_pkg(conf, 'libwebsockets', uselib_store='WEBSOCKETS', atleast_version='2.0.0', mandatory=False)
|
||||
if conf.is_defined('HAVE_WEBSOCKETS'):
|
||||
children += [ 'websockets' ]
|
||||
|
||||
for i in children:
|
||||
sub_config_and_use(conf, i)
|
||||
|
||||
@@ -101,6 +105,8 @@ def build(bld):
|
||||
bld.recurse('contourdesign')
|
||||
if bld.is_defined('BUILD_MASCHINE'):
|
||||
bld.recurse('maschine2')
|
||||
if bld.is_defined ('HAVE_WEBSOCKETS'):
|
||||
bld.recurse('websockets')
|
||||
|
||||
def shutdown():
|
||||
autowaf.shutdown()
|
||||
|
||||
92
websockets_client/css/main.css
Normal file
92
websockets_client/css/main.css
Normal file
@@ -0,0 +1,92 @@
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #282923;
|
||||
color: rgb(248,248,242);
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#strips {
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
box-shadow: 0px 0px 10px #000;
|
||||
}
|
||||
|
||||
#log {
|
||||
height: 6em;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#log pre {
|
||||
margin: 0;
|
||||
font-family: Menlo, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.message-in {
|
||||
color: rgb(166,226,44);
|
||||
}
|
||||
|
||||
.message-out {
|
||||
color: rgb(172,128,255);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: rgb(249,36,114);
|
||||
}
|
||||
|
||||
.comp-name {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.strip {
|
||||
margin: 5%;
|
||||
padding: 2.5% 5%;
|
||||
background: rgba(0,0,0,0.1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.slider-meter {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.strip-slider {
|
||||
margin-top: 2.5%;
|
||||
}
|
||||
|
||||
.plugin {
|
||||
margin: 5%;
|
||||
padding: 2.5% 5%;
|
||||
background: rgba(0,0,0,0.05);
|
||||
border: solid 1px rgba(255,255,255,0.1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.plugin-enable {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.plugin-param {
|
||||
margin: 5%;
|
||||
}
|
||||
|
||||
.plugin-param.boolean {
|
||||
display: inline-block;
|
||||
}
|
||||
50
websockets_client/css/widget.css
Normal file
50
websockets_client/css/widget.css
Normal file
@@ -0,0 +1,50 @@
|
||||
.widget-switch {
|
||||
display: block;
|
||||
-webkit-appearance:none;
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
border: 3.5px solid rgb(248,248,242);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.widget-switch:checked {
|
||||
background: rgb(235,141,33);
|
||||
}
|
||||
|
||||
.widget-slider {
|
||||
display: block;
|
||||
-webkit-appearance: none;
|
||||
height: 37px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.widget-slider::-webkit-slider-runnable-track {
|
||||
height: 4px;
|
||||
background: rgb(248,248,242);
|
||||
}
|
||||
|
||||
.widget-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
margin-top: -16px;
|
||||
border-radius: 50%;
|
||||
background: rgb(235,141,33);
|
||||
}
|
||||
|
||||
/* repeat slider style for firefox */
|
||||
|
||||
.widget-slider::-moz-range-track {
|
||||
height: 4px;
|
||||
background: rgb(248,248,242);
|
||||
}
|
||||
|
||||
.widget-slider::-moz-range-thumb {
|
||||
-webkit-appearance: none;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
margin-top: -16px;
|
||||
border-radius: 50%;
|
||||
background: rgb(235,141,33);
|
||||
}
|
||||
18
websockets_client/index.html
Normal file
18
websockets_client/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Ardour WebSockets (Experimental)</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/widget.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="strips"></div>
|
||||
<div id="log"></div>
|
||||
</div>
|
||||
<script src="js/connection.js"></script>
|
||||
<script src="js/widget.js"></script>
|
||||
<script src="js/client.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
159
websockets_client/js/client.js
Normal file
159
websockets_client/js/client.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
(() => {
|
||||
|
||||
const MAX_LOG_LINES = 1000;
|
||||
const FEEDBACK_NODES = ['strip_gain', 'strip_pan', 'strip_meter', 'strip_plugin_enable',
|
||||
'strip_plugin_param_value'];
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const host = window.location.hostname || '127.0.0.1';
|
||||
const port = urlParams.get('port') || 9000;
|
||||
const conn = new Connection(host, port);
|
||||
const widgets = {};
|
||||
|
||||
conn.messageCallback = (node, addr, val) => {
|
||||
log(`↙ ${node} (${addr}) = ${val}`, 'message-in');
|
||||
|
||||
if (node == 'strip_desc') {
|
||||
createStrip (addr, ...val);
|
||||
} else if (node == 'strip_plugin_desc') {
|
||||
createStripPlugin (addr, ...val);
|
||||
} else if (node == 'strip_plugin_param_desc') {
|
||||
createStripPluginParam (addr, ...val);
|
||||
} else if (FEEDBACK_NODES.includes(node)) {
|
||||
if (widgets[[node, addr]]) {
|
||||
widgets[[node, addr]].value = val[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
conn.closeCallback = () => {
|
||||
log('Connection dropped', 'error');
|
||||
};
|
||||
|
||||
conn.errorCallback = () => {
|
||||
log('Connection error', 'error');
|
||||
};
|
||||
|
||||
function createStrip (addr, name) {
|
||||
const id = `strip-${addr[0]}`;
|
||||
const strips = document.getElementById('strips');
|
||||
const div = createElem(`<div class="strip" id="${id}"></div>`, strips);
|
||||
createElem(`<label class="comp-name" for="${id}">∿  ${name}</label>`, div);
|
||||
|
||||
// meter
|
||||
const meter = new StripMeter('strip_meter', addr);
|
||||
meter.el.classList.add('slider-meter');
|
||||
meter.attach(div);
|
||||
register(meter);
|
||||
|
||||
// gain
|
||||
let holder = createElem(`<div class="strip-slider"></div>`, div);
|
||||
createElem(`<label>Gain</label>`, holder);
|
||||
const gain = new StripGainSlider('strip_gain', addr);
|
||||
gain.attach(holder, (val) => send(gain));
|
||||
register(gain);
|
||||
|
||||
// pan
|
||||
holder = createElem(`<div class="strip-slider"></div>`, div);
|
||||
createElem(`<label>Pan</label>`, holder);
|
||||
const pan = new StripPanSlider('strip_pan', addr);
|
||||
pan.attach(holder, (val) => send(pan));
|
||||
register(pan);
|
||||
}
|
||||
|
||||
function createStripPlugin (addr, name) {
|
||||
const strip = document.getElementById(`strip-${addr[0]}`);
|
||||
const id = `plugin-${addr[0]}-${addr[1]}`;
|
||||
const div = createElem(`<div class="plugin" id="${id}"></div>`, strip);
|
||||
createElem(`<label class="comp-name">⨍  ${name}</label>`, div);
|
||||
const enable = new Switch('strip_plugin_enable', addr);
|
||||
enable.el.classList.add('plugin-enable');
|
||||
enable.attach(div, (val) => send(enable));
|
||||
register(enable);
|
||||
}
|
||||
|
||||
function createStripPluginParam (addr, name, data_type, min, max, is_log) {
|
||||
let param, clazz;
|
||||
|
||||
if (data_type == 'b') {
|
||||
clazz = 'boolean';
|
||||
param = new Switch('strip_plugin_param_value', addr);
|
||||
} else if (data_type == 'i') {
|
||||
clazz = 'discrete';
|
||||
param = new DiscreteSlider('strip_plugin_param_value', addr, min, max);
|
||||
} else if (data_type == 'd') {
|
||||
clazz = 'continuous';
|
||||
if (is_log) {
|
||||
param = new LogarithmicSlider('strip_plugin_param_value', addr, min, max);
|
||||
} else {
|
||||
param = new ContinuousSlider('strip_plugin_param_value', addr, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = document.getElementById(`plugin-${addr[0]}-${addr[1]}`);
|
||||
const id = `param-${addr[0]}-${addr[1]}-${addr[2]}`;
|
||||
const div = createElem(`<div class="plugin-param ${clazz}" id="${id}"></div>`, plugin);
|
||||
createElem(`<label for="${id}">${name}</label>`, div);
|
||||
|
||||
param.attach(div, (val) => send(param));
|
||||
param.el.name = id;
|
||||
register(param);
|
||||
}
|
||||
|
||||
function send (widget) {
|
||||
const val = widget.value;
|
||||
log(`↗ ${widget.node} (${widget.addr}) = ${val}`, 'message-out');
|
||||
conn.send(widget.node, widget.addr, [val]);
|
||||
}
|
||||
|
||||
function createElem (html, parent) {
|
||||
const t = document.createElement('template');
|
||||
t.innerHTML = html;
|
||||
|
||||
const elem = t.content.firstChild;
|
||||
|
||||
if (parent) {
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function register (widget) {
|
||||
widgets[widget.hash] = widget;
|
||||
}
|
||||
|
||||
function log (message, className) {
|
||||
const output = document.getElementById('log');
|
||||
|
||||
if (output.childElementCount > MAX_LOG_LINES) {
|
||||
output.removeChild(output.childNodes[0]);
|
||||
}
|
||||
|
||||
const pre = document.createElement('pre');
|
||||
pre.innerHTML = message;
|
||||
pre.className = className;
|
||||
|
||||
output.appendChild(pre);
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
})();
|
||||
75
websockets_client/js/connection.js
Normal file
75
websockets_client/js/connection.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const JSON_INF = 1.0e+128;
|
||||
|
||||
class Connection {
|
||||
|
||||
constructor (host, port) {
|
||||
this.socket = new WebSocket(`ws://${host}:${port}`);
|
||||
this.socket.onopen = () => this.openCallback();
|
||||
this.socket.onclose = () => this.closeCallback();
|
||||
this.socket.onerror = (error) => this.errorCallback(error);
|
||||
this.socket.onmessage = (event) => this._onMessage(event);
|
||||
}
|
||||
|
||||
openCallback () {
|
||||
// empty
|
||||
}
|
||||
|
||||
closeCallback () {
|
||||
// empty
|
||||
}
|
||||
|
||||
errorCallback (error) {
|
||||
// empty
|
||||
}
|
||||
|
||||
messageCallback (node, addr, val) {
|
||||
// empty
|
||||
}
|
||||
|
||||
send (node, addr, val) {
|
||||
for (const i in val) {
|
||||
if (val[i] == Infinity) {
|
||||
val[i] = JSON_INF;
|
||||
} else if (val[i] == -Infinity) {
|
||||
val[i] = -JSON_INF;
|
||||
}
|
||||
}
|
||||
|
||||
const json = JSON.stringify({node: node, addr: addr, val: val});
|
||||
|
||||
this.socket.send(json);
|
||||
}
|
||||
|
||||
_onMessage (event) {
|
||||
const msg = JSON.parse(event.data);
|
||||
|
||||
for (const i in msg.val) {
|
||||
if (msg.val[i] >= JSON_INF) {
|
||||
msg.val[i] = Infinity;
|
||||
} else if (msg.val[i] <= -JSON_INF) {
|
||||
msg.val[i] = -Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
this.messageCallback(msg.node, msg.addr || [], msg.val);
|
||||
}
|
||||
|
||||
}
|
||||
157
websockets_client/js/widget.js
Normal file
157
websockets_client/js/widget.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Luciano Iam <lucianito@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Widget {
|
||||
|
||||
constructor (node, addr, html) {
|
||||
this.node = node;
|
||||
this.addr = addr;
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = html;
|
||||
this.el = template.content.firstChild;
|
||||
}
|
||||
|
||||
attach (parent, callback) {
|
||||
parent.appendChild(this.el);
|
||||
|
||||
if (callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
callback (value) {
|
||||
// do nothing by default
|
||||
}
|
||||
|
||||
get hash () {
|
||||
return [this.node, this.addr];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Switch extends Widget {
|
||||
|
||||
constructor (node, addr) {
|
||||
super (node, addr, `<input type="checkbox" class="widget-switch">`);
|
||||
this.el.addEventListener('input', (ev) => this.callback(this.value));
|
||||
}
|
||||
|
||||
get value () {
|
||||
return this.el.checked;
|
||||
}
|
||||
|
||||
set value (val) {
|
||||
this.el.checked = val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Slider extends Widget {
|
||||
|
||||
constructor (node, addr, min, max, step) {
|
||||
const html = `<input type="range" class="widget-slider"
|
||||
min="${min}" max="${max}" step="${step}">`;
|
||||
super(node, addr, html);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.el.addEventListener('input', (ev) => this.callback(this.value));
|
||||
}
|
||||
|
||||
get value () {
|
||||
return parseFloat(this.el.value)
|
||||
}
|
||||
|
||||
set value (val) {
|
||||
this.el.value = val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DiscreteSlider extends Slider {
|
||||
|
||||
constructor (node, addr, min, max) {
|
||||
super(node, addr, min, max, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ContinuousSlider extends Slider {
|
||||
|
||||
constructor (node, addr, min, max) {
|
||||
super(node, addr, min, max, 0.001);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LogarithmicSlider extends ContinuousSlider {
|
||||
|
||||
constructor (node, addr, min, max) {
|
||||
super(node, addr, 0, 1.0);
|
||||
this.minVal = Math.log(min);
|
||||
this.maxVal = Math.log(max);
|
||||
this.scale = this.maxVal - this.minVal;
|
||||
}
|
||||
|
||||
get value () {
|
||||
return Math.exp(this.minVal + this.scale * super.value);
|
||||
}
|
||||
|
||||
set value (val) {
|
||||
this.el.value = (Math.log(val) - this.minVal) / this.scale;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StripPanSlider extends ContinuousSlider {
|
||||
|
||||
constructor (node, addr) {
|
||||
super(node, addr, -1.0, 1.0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StripGainSlider extends ContinuousSlider {
|
||||
|
||||
constructor (node, addr) {
|
||||
super(node, addr, 0, 1.0)
|
||||
this.minVal = -58.0;
|
||||
this.maxVal = 6.0;
|
||||
this.scale = (this.maxVal - this.minVal);
|
||||
}
|
||||
|
||||
get value () {
|
||||
return this.maxVal + Math.log10(super.value) * this.scale;
|
||||
}
|
||||
|
||||
set value (val) {
|
||||
this.el.value = Math.pow(10.0, (val - this.maxVal) / this.scale);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StripMeter extends Widget {
|
||||
|
||||
constructor (node, addr) {
|
||||
super(node, addr, `<label></label>`);
|
||||
}
|
||||
|
||||
set value (val) {
|
||||
this.el.innerHTML = val == -Infinity ? '-∞' : `${Math.round(val)} dB`;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user