diff --git a/configure.ac b/configure.ac index 98d3f29c..edb035eb 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,14 @@ PKG_CHECK_MODULES([LIBXML], [libxml-2.0 >= 2.0], LIBS="$LIBS $LIBXML_LIBS" ]) +dnl Checking for liblo +PKG_CHECK_MODULES([LIBLO], [liblo >= 0.28], + [ + PKG_FLAGS="$PKG_FLAGS $LIBLO_CFLAGS" + LIBS="$LIBS $LIBLO_LIBS" + ] +) + dnl by the way, AC_HELP_STRING is deprecated, use AS_HELP_STRING instead! ENABLE_EXPLICIT([debugging],[debugging symbols, asserts, ...]) @@ -521,6 +529,20 @@ ENABLE_FORCED([ip-interface], [network (TCP/IP) interface (needs Asio, https://t AC_CHECK_HEADER([asio.hpp], , [have_ip_interface=no]) ]) +ENABLE_FORCED([osc-interface], [network OSC interface], +[ + AC_CHECK_HEADER([lo/lo.h], , [have_osc_interface=no]) + AC_CHECK_HEADER([lo/lo_cpp.h], , [have_osc_interface=no]) + AC_MSG_RESULT([$have_osc_interface]) +]) + +ENABLE_FORCED([osc-interface], [network OSC interface], +[ + AC_CHECK_HEADER([lo/lo.h], , [have_osc_interface=no]) + AC_CHECK_HEADER([lo/lo_cpp.h], , [have_osc_interface=no]) + AC_MSG_RESULT([$have_osc_interface]) +]) + ENABLE_FORCED([ecasound], [Ecasound soundfile playback/recording], [ dnl Checking for libecasoundc. It does not provide pkg-config and installs @@ -763,6 +785,7 @@ echo "| VRPN ................................ : $have_vrpn" echo "|" echo "| Build with Ecasound support ............ : $have_ecasound" echo "| Build with IP interface ................ : $have_ip_interface" +echo "| Build with OSC interface ............... : $have_osc_interface" echo "| Build with GUI ......................... : $gui_string" echo "|" echo "| Enable debugging/optimization .......... : $have_debugging/$have_optimization" diff --git a/data/ssr.conf.example b/data/ssr.conf.example index eca99e30..06c74fd1 100644 --- a/data/ssr.conf.example +++ b/data/ssr.conf.example @@ -95,6 +95,17 @@ # carriage return: 13) #END_OF_MESSAGE_CHARACTER = 10 +############################ Networking configuration ########################## + +# networking mode: client, server (default: client) +#NETWORK_MODE = server + +# networking port for sending (default: 50001) +#OSC_PORT = 50001 + +# networking clients, comma-separated by hostname:port +#NETWORK_CLIENTS = "client:50001, client:50001" + ############################## Verbosity Level ################################# # Set the level of system information diff --git a/src/Makefile.am b/src/Makefile.am index e4400861..8a814dfa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -182,6 +182,20 @@ SSRSOURCES += \ network/server.h endif +if ENABLE_OSC_INTERFACE +AM_CPPFLAGS += -I$(srcdir)/networking + +SSRSOURCES += \ + networking/oscclient.cpp \ + networking/oscclient.h \ + networking/oschandler.cpp \ + networking/oschandler.h \ + networking/oscreceiver.cpp \ + networking/oscreceiver.h \ + networking/oscsender.cpp \ + networking/oscsender.h +endif + if ENABLE_GUI AM_CPPFLAGS += -I$(srcdir)/gui diff --git a/src/configuration.cpp b/src/configuration.cpp index 141fdd39..a8965d8e 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -38,6 +38,7 @@ #include #include // std::this_thread::sleep_for #include // std::chrono::seconds +#include // for parse_network_clients #include "configuration.h" #include "posixpathtools.h" @@ -108,6 +109,100 @@ namespace // anonymous } } +/** + * Function to retrieve a valid port from a char. + * Returns an ephemeral port according to IANA suggestions + * @param port a const char* holding a number + * @return An ephemeral port according to IANA suggestions + */ +int ssr::get_valid_network_port(const char* port) +{ + int port_number = atoi(port); + bool valid_port = true; + if (port_number < 1024 && port_number > 0) + { + ERROR("Port number is in the range of well-known ports!"); + valid_port = false; + } + else if(port_number < 0) + { + ERROR("Port number must not be negative!"); + valid_port = false; + } + else if (port_number < 49152 || port_number > 65535) + { + ERROR("Port number is not in the range of ephemeral ports suggested by IANA!"); + valid_port = false; + } + if (!valid_port) + { + WARNING("Using standard port."); + port_number = 50001; + } + return port_number; +} + +/* Function to remove all whitespaces from a string. + * If the string is " " it will return an empty string. + * @param str a reference to a std::string + * @return a std::string without whitespaces + */ +std::string ssr::remove_whitespace(const std::string& str) +{ + if (str == " ") return ""; + size_t first = str.find_first_not_of(' '); + if (std::string::npos == first) + { + return str; + } + size_t last = str.find_last_not_of(' '); + return str.substr(first, (last - first + 1)); +} + +/** + * Function to retrieves tuples of key value pairs from a comma-separated + * string and stores it in a multimap. The tuples should be of the form + * client:port, client2:port2, etc.. + * @param input a const char* holding a comma-separated list of clients:port + * tuples + * @param clients reference to a std::multimap to store + * information on client and port in. + * @return CONFIG_SUCCESS on successful completion + */ +static int parse_network_clients(const char* input, + std::multimap& clients) +{ + std::istringstream iss(input); + std::string name; + int port; + std::string token; + + while (std::getline(iss, token, ',')) { + size_t pos = token.find(':'); + std::string port_temp = ssr::remove_whitespace(token.substr(pos+1)); + name = ssr::remove_whitespace(token.substr(0, pos)); + + if (!name.empty()) + { + // if no port supplied, insert standard + if ( port_temp.empty() || port_temp == name ) + { + port = 50001; + } + else + { + port = std::stoi(port_temp); + } + clients.insert(make_pair(name, port)); + } + } + VERBOSE("Read the following clients:"); + for (const auto& client: clients) { + VERBOSE(client.first << ":" << client.second); + } + return CONFIG_SUCCESS; +} + /** parse command line options and configuration file(s) * @param argc number of command line arguments. * @param argv the arguments themselves. @@ -157,6 +252,10 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) conf.renderer_params.set("decay_exponent", 1.0f); // 1 / r^1 conf.renderer_params.set("amplitude_reference_distance", 3.0f); // meters + // default network settings, also stated in data/ssr.conf.example + conf.network_mode = "client"; + conf.osc_port = 50001; + conf.auto_rotate_sources = true; // for WFS renderer @@ -240,6 +339,14 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) " --no-auto-rotation\n" " Don't auto-rotate sound sources' orientation toward " "the reference\n" +" -N --network-mode=VALUE\n" +" Which network mode to use: client or server " + "(default: client)\n" +" -C --network-clients=VALUE\n" +" List of network clients and their ports (e.g. " + "client1:50001, client2:50001)\n" +" -p --osc-port=VALUE\n" +" Port to use for OSC communication (default: 50001)\n" #ifdef ENABLE_IP_INTERFACE " -i, --ip-server[=PORT]\n" @@ -323,6 +430,9 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) {"master-volume-correction", required_argument, nullptr, 0}, {"auto-rotation", no_argument, nullptr, 0 }, {"no-auto-rotation", no_argument, nullptr, 0 }, + {"network-mode", required_argument, nullptr, 'N'}, + {"network-clients", required_argument, nullptr, 'C'}, + {"osc-port", required_argument, nullptr, 'p'}, {"ip-server", optional_argument, nullptr, 'i'}, {"no-ip-server", no_argument, nullptr, 'I'}, {"end-of-message-character", required_argument, nullptr, 0}, @@ -341,7 +451,7 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) }; // one colon: required argument; two colons: optional argument // if first character is '-', non-option arguments return 1 (see case 1 below) - const char *optstring = "-c:fgGhi::In:o:r:s:t:TvV?"; + const char *optstring = "-c:C:fgGhi::IN:n:o:p:r:s:t:TvV?"; int opt; int longindex = 0; @@ -441,6 +551,9 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) + std::string(optarg) + "\"!"); } break; + case 'C': + parse_network_clients(optarg, conf.network_clients); + break; case 'f': conf.freewheeling = true; @@ -478,6 +591,16 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) conf.ip_server = false; break; + case 'N': + //TODO: check if correct string + if (!strcasecmp(optarg, "client") || !strcasecmp(optarg, "server")) + { + conf.network_mode = optarg; + } + else { + ERROR("'"<< optarg << "' is not understood as option for network-mode."); + } + break; case 'n': conf.renderer_params.set("name", optarg); break; @@ -485,6 +608,9 @@ ssr::conf_struct ssr::configuration(int& argc, char* argv[]) case 'o': conf.renderer_params.set("ambisonics_order", atoi(optarg)); break; + case 'p': + conf.osc_port = get_valid_network_port(optarg); + break; case 'r': conf.audio_recorder_file_name = optarg; @@ -558,6 +684,7 @@ static int is_comment_or_empty(const char *line){ return (*line == '#') || (!*line); } + /******************************************************************************/ /* This function takes a line from the configuration file and splits @@ -790,6 +917,24 @@ int ssr::load_config_file(const char *filename, conf_struct& conf){ if (!strcasecmp(value, "on")) conf.gui = true; else conf.gui = false; } + else if (!strcmp(key, "NETWORK_MODE")) + { + if (!strcasecmp(value, "client") || !strcasecmp(value, "server")) + { + conf.network_mode = value; + } + else { + ERROR("'"<< value << "' is not understood as option for network-mode."); + } + } + else if (!strcmp(key, "NETWORK_CLIENTS")) + { + parse_network_clients(value, conf.network_clients); + } + else if (!strcmp(key, "OSC_PORT")) + { + conf.osc_port = get_valid_network_port(value); + } else if (!strcmp(key, "NETWORK_INTERFACE")) { #ifdef ENABLE_IP_INTERFACE diff --git a/src/configuration.h b/src/configuration.h index f4e0803d..000fe7bb 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -30,6 +30,7 @@ #ifndef SSR_CONFIGURATION_H #define SSR_CONFIGURATION_H +#include #include #include "apf/parameter_map.h" @@ -57,6 +58,10 @@ struct conf_struct std::string output_port_prefix; ///< e.g. "alsa_pcm:playback" std::string path_to_gui_images; ///< dto. std::string path_to_scene_menu; ///< path to scene_menu.conf + std::string network_mode; ///< network mode (client or server) + ///< list of network clients and ports + std::multimap network_clients; + int osc_port; ///< osc communication port int end_of_message_character; ///< ASCII bool auto_rotate_sources; ///< Automatic orientation of sources @@ -79,6 +84,8 @@ conf_struct configuration(int& argc, char* argv[]); // static int parse(const char *line, char *key, char *value); // static int is_comment_or_empty(const char *line); int load_config_file(const char *filename, conf_struct& conf); +std::string remove_whitespace(const std::string& str); +int get_valid_network_port(const char* port); } // namespace ssr diff --git a/src/controller.h b/src/controller.h index 823976d0..abe5c2dc 100644 --- a/src/controller.h +++ b/src/controller.h @@ -61,6 +61,10 @@ #include "server.h" #endif +#ifdef ENABLE_OSC_INTERFACE +#include "oschandler.h" +#endif + #include "tracker.h" #ifdef ENABLE_INTERSENSE #include "trackerintersense.h" @@ -253,6 +257,11 @@ class Controller : public Publisher #ifdef ENABLE_IP_INTERFACE std::unique_ptr _network_interface; #endif + +#ifdef ENABLE_OSC_INTERFACE + std::unique_ptr _osc_interface; +#endif + std::unique_ptr _tracker; /// check if audio player is running and start it if necessary @@ -331,6 +340,16 @@ Controller::Controller(int argc, char* argv[]) } #endif +// throw error, if OSC interface is about to be used, but not compiled in +#ifndef ENABLE_OSC_INTERFACE + if (_conf.network_mode == "client" || _conf.network_mode == "server") + { + throw std::logic_error(_conf.exec_name + + " was compiled without OSC support!\n" + "Type '" + _conf.exec_name + " --help' for more information."); + } +#endif + #ifndef ENABLE_GUI if (_conf.gui) { @@ -403,6 +422,19 @@ Controller::Controller(int argc, char* argv[]) _network_interface->start(); } #endif // ENABLE_IP_INTERFACE + +// if OSC is compiled in and network-mode set, start OSC handler +#ifdef ENABLE_OSC_INTERFACE + if (_conf.network_mode == "client" || _conf.network_mode == "server") + { + VERBOSE2("Starting OSC interface as "<< _conf.network_mode << + " on port " << + std::to_string(_conf.osc_port) << "."); + _osc_interface.reset(new OscHandler(*this, _conf.osc_port, + _conf.network_mode, _conf.network_clients)); + _osc_interface->start(); + } +#endif } template diff --git a/src/networking/oscclient.cpp b/src/networking/oscclient.cpp new file mode 100644 index 00000000..e8c2267d --- /dev/null +++ b/src/networking/oscclient.cpp @@ -0,0 +1,147 @@ +/** + * Implementation of oscclient.h + * @file oscclient.cpp + */ + +#include "oscclient.h" + +/** + * Constructor used to create OSC client objects + * @param hostname a std::string used in the object's address + * @param port a std::string used in the object's address + * @param message_level the MessageLevel defining the clients level of messages + */ +ssr::OscClient::OscClient(std::string hostname, std::string port, MessageLevel + message_level) + : _address(hostname, port) + , _message_level(message_level) + , _alive_counter(10) +{ + _active = true; + VERBOSE("OscClient: Initialized as '" << _address.hostname() << ":" << + _address.port() << "'."); +} + +/** + * Destructor + **/ +ssr::OscClient::~OscClient() +{} + +/** + * Function to get the OscClient's _message_level. + * @return message_level a MessageLevel used for the OscClient + **/ +ssr::MessageLevel ssr::OscClient::message_level() +{ + return _message_level; +} + +/** + * Function to get the OscClient's _active state + * @return a bool, representing the OscClient's state + **/ +bool ssr::OscClient::active() +{ + return _active; +} + +/** + * Function to set the OscClient's _active state to false + **/ +void ssr::OscClient::deactivate() +{ + _active = false; +} + +/** + * Function to set the OscClient's _active state to true + **/ +void ssr::OscClient::activate() +{ + _active = true; +} + + +/** + * Function to get the OscClient's _address + * @return a lo::Address used for the OscClient + **/ +lo::Address& ssr::OscClient::address() +{ + return _address; +} + +/** + * Function to set the OscClient's _address. + * @param hostname reference to a std::string representing the hostname to be + * used + * @param port reference to a std::string& representing the port to be used + **/ +void ssr::OscClient::set_address(std::string& hostname, std::string& port) +{ + _address = lo::Address(hostname, port); + VERBOSE3("OscClient: Address changed to: " << _address.hostname() << ":" << + _address.port() << "."); +} + +/** + * Function to set the OscClient's _message_level. + * @param message_level a MessageLevel to be used for the OscClient + **/ +void ssr::OscClient::set_message_level(MessageLevel message_level) +{ + _message_level = message_level; + VERBOSE("OscClient: Message level of '" << _address.hostname() << ":" << + _address.port() << "' changed to " << static_cast(_message_level) + << "."); +} + +/** + * Function to get the OscClient's hostname. + * @return std::string representing the OscClient's hostname + **/ +const std::string ssr::OscClient::hostname() +{ + return _address.hostname(); +} + +/** + * Function to get the OscClient's port. + * @return std::string representing the OscClient's port + **/ +const std::string ssr::OscClient::port() +{ + return _address.port(); +} + +/** + * Function to increment the OscClient's _alive_counter + **/ +void ssr::OscClient::increment_alive_counter() +{ + _alive_counter++; +} + +/** + * Function to decrement the OscClient's _alive_counter. + * Deactivates the OscClient, in the case where _alive_counter <= 0 + **/ +void ssr::OscClient::decrement_alive_counter() +{ + _alive_counter--; + if(_alive_counter < 0) + { + deactivate(); + VERBOSE("OscClient: Deactivated '" << _address.hostname() << ":" << + _address.port() << "' due to inactivity."); + } +} + +/** + * Function to reset the OscClient's _alive counter + **/ +void ssr::OscClient::reset_alive_counter() +{ + _alive_counter=10; +} diff --git a/src/networking/oscclient.h b/src/networking/oscclient.h new file mode 100644 index 00000000..fa0a1628 --- /dev/null +++ b/src/networking/oscclient.h @@ -0,0 +1,44 @@ +/** + * Header for OscClient, defining a class, holding client information + * @file oscclient.h + */ + +#ifndef OSC_CLIENT_H +#define OSC_CLIENT_H + +#include +#include +#include "ssr_global.h" // for VERBOSE, MessageLevel + +namespace ssr +{ + +class OscClient +{ + private: + lo::Address _address; + MessageLevel _message_level; + bool _active = false; + int _alive_counter; + + public: + OscClient(std::string hostname, std::string port, MessageLevel + message_level); + ~OscClient(); + + ssr::MessageLevel message_level(); + lo::Address& address(); + bool active(); + void activate(); + void deactivate(); + void set_address(std::string& hostname, std::string& port); + void set_message_level(MessageLevel message_level); + const std::string hostname(); + const std::string port(); + void increment_alive_counter(); + void decrement_alive_counter(); + void reset_alive_counter(); +}; + +} +#endif diff --git a/src/networking/oschandler.cpp b/src/networking/oschandler.cpp new file mode 100644 index 00000000..d4a8ac70 --- /dev/null +++ b/src/networking/oschandler.cpp @@ -0,0 +1,319 @@ +/** + * Implementation of oschandler.h + * @file oschandler.cpp + */ + +#include "oschandler.h" +#include + +/** + * Constructor + * @param controller reference to a Publisher object + * @param port_in an integer describing the port number used for incoming + * traffic + * @param port_out an integer describing the port number used for outgoing + * traffic + * @param mode a string defining the mode (client|server) + * @param clients a multimap holding hostname, port pairs (only used for + * server) + */ +ssr::OscHandler::OscHandler(Publisher& controller, int port, std::string mode, + std::multimap clients) + : _mode(mode) + , _controller(controller) + , _server(port) + , _osc_receiver(controller, *this) + , _osc_sender(controller, *this) +{ + VERBOSE("OscHandler: Initialized."); + if (mode == "server") + { + VERBOSE("OscHandler: " << clients.size() << " client(s)."); + for (const auto& client: clients) + { + _osc_sender.add_client(client.first, std::to_string(client.second), + ssr::MessageLevel::CLIENT); + } + } +} + +/** + * Destructor + */ +ssr::OscHandler::~OscHandler() +{ + stop(); + VERBOSE("OscHandler: Destructing."); +} + +/** + * Stop this OscHandler by stopping its OscReceiver and OscSender + */ +void ssr::OscHandler::stop() +{ + VERBOSE("OscHandler: Stopping"); + _osc_receiver.stop(); + _osc_sender.stop(); +} + +/** + * Start this OscHandler by starting its OscReceiver and OscSender + */ +void ssr::OscHandler::start() +{ + VERBOSE("OscHandler: Starting"); + // check if lo::ServerThread is valid + if (!_server.is_valid()) { + ERROR("OscHandler: The liblo ServerThread could not be started!" << + " Is the port already in use?"); + exit(EXIT_FAILURE); + } + _server.set_callbacks([this]() + { + VERBOSE("OscHandler: Started ServerThread."); + }, + []() + { + VERBOSE2("OscHandler: ServerThread cleanup."); + } + ); + VERBOSE("OscHandler: url = " << _server.url() << "."); + _osc_receiver.start(); + _osc_sender.start(); + _server.start(); +} + +/** + * OscHandler's friend function to set the OscSender's server_address + * @param self reference to OscHandler holding OscSender + * @param hostname reference to std::string representing hostname + * @param port reference to std::string representing port + */ +void ssr::OscReceiver::set_server_address(ssr::OscHandler& self, + std::string& hostname, std::string& port) +{ + self._osc_sender.set_server_address(hostname, port); +} + +/** + * OscHandler's friend function to set a the OscSender _server's message_level + * @param self reference to OscHandler holding OscSender + * @param hostname reference to std::string representing the client's hostname + * @param port reference to std::string representing the client's port + * @param message_level ssr::MessageLevel to be used for server + */ +void ssr::OscReceiver::set_server_message_level(OscHandler& self, + ssr::MessageLevel message_level) +{ + self._osc_sender.set_server_message_level(message_level); +} + +/** + * OscHandler's friend function to return OscSender's server_address + * @param self reference to OscHandler holding the OscSender + * @return lo::Address server_address of OscSender + */ +lo::Address ssr::OscReceiver::server_address(ssr::OscHandler& self) +{ + return self._osc_sender.server_address(); +} + +/** + * OscHandler's friend function to check, if the current lo::Address of the + * server is the default + * @see ssr::OscSender::server_is_default() + * @param self reference to OscHandler holding the OscSender + * @return bool true, if OscSender's server address is the default, false + * otherwise + */ +bool ssr::OscReceiver::server_is_default(ssr::OscHandler& self) +{ + return self._osc_sender.server_is_default(); +} + +/** + * OscHandler's friend function to check, if the provided hostname and port + * match the OscSender's _server lo::Address + * @param self reference to OscHandler holding the OscSender + * @param hostname reference to std::string representing the hostname + * @param port reference to std::string representing the port + * @return bool true, if OscSender's server address matches the provided + * hostname and port + */ +bool ssr::OscReceiver::is_server(ssr::OscHandler& self, std::string& hostname, + std::string& port) +{ + return self._osc_sender.is_server(hostname, port); +} + +/** + * OscHandler's friend function to add a client to the list of OscSender's + * _client_addresses. + * @param self reference to OscHandler holding OscSender + * @param hostname std::string representing the client's hostname + * @param port std::string representing the client's port + * @param message_level ssr::MessageLevel representing the client's message + * level + */ +void ssr::OscReceiver::add_client(OscHandler& self, std::string hostname, + std::string port, ssr::MessageLevel message_level = + ssr::MessageLevel::CLIENT) +{ + self._osc_sender.add_client(hostname, port, message_level); +} + +/** + * OscHandler's friend function to deactivate a client from the list of + * OscSender's _clients + * @param self reference to OscHandler holding OscSender + * @param hostname std::string representing the client's hostname + * @param port std::string representing the client's port + */ +void ssr::OscReceiver::deactivate_client(OscHandler& self, std::string + hostname, std::string port) +{ + self._osc_sender.deactivate_client(hostname, port); +} + +/** + * OscHandler's friend function to set a client's message_level + * @param self reference to OscHandler holding OscSender + * @param hostname std::string representing the client's hostname + * @param port std::string representing the client's port + * @param message_level ssr::MessageLevel to be used for client + */ +void ssr::OscReceiver::set_client_message_level(OscHandler& self, std::string + hostname, std::string port, ssr::MessageLevel message_level) +{ + self._osc_sender.set_client_message_level(hostname, port, message_level); +} + +/** + * OscHandler's friend function to check if a client's message_level is greater + * or equal to one provided. + * @param self reference to OscHandler holding OscSender + * @param hostname std::string representing the client's hostname + * @param port std::string representing the client's port + * @param message_level ssr::MessageLevel to check for + * @return true, if the MessageLevel is equal or greater than the one + * requested, false otherwise + */ +bool ssr::OscReceiver::client_has_message_level(OscHandler& self, std::string& + hostname, std::string& port, ssr::MessageLevel message_level) +{ + return self._osc_sender.client_has_message_level(hostname, port, message_level); +} + +/** + * OscHandler's friend function to increment an active client's alive counter. + * @param self reference to OscHandler holding OscSender + * @param hostname std::string representing the client's hostname + * @param port std::string representing the client's port + */ +void ssr::OscReceiver::increment_client_alive_counter(OscHandler& self, + std::string hostname, std::string port) +{ + self._osc_sender.increment_client_alive_counter(hostname, port); +} + + +/** + * This function returns the OscHandler's mode + * @return std::string (either server or client) + */ +std::string ssr::OscHandler::mode() +{ + return _mode; +} + +/** + * Returns true, if the instance of OscHandler is a 'client', false otherwise. + * @return true, if _oschandler.mode() returns 'client', false otherwise. + */ +bool ssr::OscHandler::is_client() +{ + if(!_mode.compare("client")) + { + return true; + } + else + { + return false; + } +} + +/** + * Returns true, if the instance of OscHandler is a 'server', false otherwise. + * @return true, if _oschandler.mode() returns 'server', false otherwise. + */ +bool ssr::OscHandler::is_server() +{ + if(!_mode.compare("server")) + { + return true; + } + else + { + return false; + } +} + +/** + * Return reference to OscHandler's lo::ServerThread + * @return lo::ServerThread& reference to _server + */ +lo::ServerThread& ssr::OscHandler::server() +{ + return _server; +} + +/** + * Returns a std::string representing the message type of a boolean type + * @param message bool + * @return _message_type_true if message true, _message_type_false otherwise + */ +const std::string ssr::OscHandler::bool_to_message_type(const bool& message) +{ + if(message) + { + return _message_type_true; + } + else + { + return _message_type_false; + } +} + +/** + * Returns a std::string representing the string of a boolean type + * @param message bool + * @return 'true' if message true, 'false' otherwise + */ +const std::string ssr::OscHandler::bool_to_string(const bool& message) +{ + if(message) + { + return _string_true; + } + else + { + return _string_false; + } +} + +/** + * Returns a std::string representing from where messages where received from + * on this instance. Depends on whether this instance is a server or a client. + * @return 'client' if _mode is "server", 'server' otherwise + */ +const std::string ssr::OscHandler::from_is() +{ + if(is_server()) + { + return _string_client; + } + else + { + return _string_server; + } +} diff --git a/src/networking/oschandler.h b/src/networking/oschandler.h new file mode 100644 index 00000000..306c3db3 --- /dev/null +++ b/src/networking/oschandler.h @@ -0,0 +1,89 @@ +/** + * Header for OscHandler, defining a class, responsible for sending OSC messages + * and subscribing to the SSR's Publisher. + * @file oschandler.h + */ + +#ifndef OSC_HANDLER_H +#define OSC_HANDLER_H + +#ifdef HAVE_CONFIG_H +#include // for ENABLE_* +#endif + +#include +#include +#include +#include "ssr_global.h" // for VERBOSE, MessageLevel +#include "oscreceiver.h" +#include "oscsender.h" + +namespace +{ + const std::string _message_type_false{"F"}; + const std::string _message_type_true{"T"}; + const std::string _string_false{"false"}; + const std::string _string_true{"true"}; + const std::string _string_server{"server"}; + const std::string _string_client{"client"}; +} + +namespace ssr +{ + +struct Publisher; + +/** + * OscHandler + * This class holds a Subscriber derivate (OscSender), which turns actions of + * the referenced Publisher into OSC messages to clients or a server and an + * OscReceiver, responsible for callbacks on arriving OSC messages, that are + * delegated to the Publisher reference. + */ +class OscHandler +{ + private: + std::string _mode; //< mode: client|server + Publisher& _controller; //< reference to Publisher object + lo::ServerThread _server; //< ServerThread used for OSC communication + OscReceiver _osc_receiver; + OscSender _osc_sender; + + public: + OscHandler(Publisher& controller, int port, std::string mode, + std::multimap clients); + ~OscHandler(); + void start(); + void stop(); + std::string mode(); + bool is_client(); + bool is_server(); + lo::ServerThread& server(); + const std::string bool_to_message_type(const bool& message); + const std::string bool_to_string(const bool& message); + const std::string from_is(); + // OscReceiver friend functions + friend void OscReceiver::set_server_address(OscHandler& self, + std::string& hostname, std::string& port); + friend void OscReceiver::set_server_message_level(OscHandler& self, + MessageLevel message_level); + friend lo::Address OscReceiver::server_address(OscHandler& self); + friend bool OscReceiver::server_is_default(OscHandler& self); + friend bool OscReceiver::is_server(OscHandler& self, std::string& hostname, + std::string& port); + friend void OscReceiver::add_client(OscHandler& self, std::string hostname, + std::string port, ssr::MessageLevel message_level); + friend void OscReceiver::deactivate_client(OscHandler& self, std::string + hostname, std::string port); + friend void OscReceiver::set_client_message_level(OscHandler& self, + std::string hostname, std::string port, ssr::MessageLevel + message_level); + friend bool OscReceiver::client_has_message_level(OscHandler& self, + std::string& hostname, std::string& port, ssr::MessageLevel + message_level); + friend void OscReceiver::increment_client_alive_counter(OscHandler& self, + std::string hostname, std::string port); +}; + +} // namespace ssr +#endif diff --git a/src/networking/oscreceiver.cpp b/src/networking/oscreceiver.cpp new file mode 100644 index 00000000..6b280049 --- /dev/null +++ b/src/networking/oscreceiver.cpp @@ -0,0 +1,1719 @@ +/** + * Implementation of oscreceiver.h + * @file oscreceiver.cpp + */ + +#include "oscreceiver.h" +#include "oschandler.h" +#include "publisher.h" +#include "apf/stringtools.h" +#include "apf/math.h" + +using namespace apf::str; + +/** + * Constructor + * @param controller reference to a Publisher object + * @param port_in integer representing a port to used for incoming OSC messages + */ +ssr::OscReceiver::OscReceiver(Publisher& controller, OscHandler& handler) + : _controller(controller) + , _handler(handler) +{ + VERBOSE("OscReceiver: Initialized."); +} + +/** + * Destructor + */ +ssr::OscReceiver::~OscReceiver() +{ + VERBOSE("OscReceiver: Destructing."); +} + +/** + * Starts the OscReceiver, by adding client/ server callback functions. + */ +void ssr::OscReceiver::start() +{ + VERBOSE("OscReceiver: Starting."); + // add method handlers for received messages + if (_handler.is_server()) + { + add_client_to_server_methods(); + add_processing_methods(); + add_reference_methods(); + add_scene_methods(); + add_source_methods(); + add_tracker_methods(); + add_transport_methods(); + add_update_notification_methods(); + } + else if (_handler.is_client()) + { + add_server_to_client_methods(); + add_processing_methods(); + add_reference_methods(); + add_scene_methods(); + add_source_methods(); + add_tracker_methods(); + add_transport_methods(); + } +} + +/** + * Stops the OscReceiver + */ +void ssr::OscReceiver::stop() +{ + VERBOSE("OscReceiver: Stopping."); +} + +/** + * Adds callback handlers (for server) for alive, subscribe and message level + * messages received from clients. + * This function uses C++11 lambda functions to define the behavior for every + * callback. + */ +void ssr::OscReceiver::add_client_to_server_methods() +{ + // incrementing alive_counter of subscribed client: "/alive" + _handler.server().add_method("/alive", NULL, [this](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/alive] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + increment_client_alive_counter(_handler, message.source().hostname(), + message.source().port()); + } + ); + VERBOSE("OscReceiver: Added callback for /alive."); + + // setting MessageLevel of subscribed client: "/message_level, {i,ssi}" + _handler.server().add_method("/message_level", NULL, [this](lo_arg **argv, + int, lo::Message message) + { + (void) argv; + if(!message.types().compare("i")) + { + VERBOSE2("OscReceiver: Got [/message_level, " << argv[0]->i << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + set_client_message_level(_handler, message.source().hostname(), + message.source().port(), get_sane_message_level(argv[0]->i)); + } + else if(!message.types().compare("ssi")) + { + std::string hostname(&argv[0]->s); + std::string port(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/message_level, " << hostname << ", " << + port << ", " << argv[2]->i << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + set_client_message_level(_handler, hostname, port, + get_sane_message_level(argv[2]->i)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /message_level {i,ssi}."); + + // subscribing and unsubscribing clients + _handler.server().add_method("/subscribe", NULL, [this](lo_arg **argv, int, + lo::Message message) + { + if(!message.types().compare("T")) + { + // subscribing client: "/subscribe, T" + VERBOSE2("OscReceiver: Got [/subscribe, " << + _handler.bool_to_string(true) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + add_client(_handler, message.source().hostname(), + message.source().port(), ssr::MessageLevel::CLIENT); + } + // unsubscribing client: "/subscribe, F" + else if(!message.types().compare("F")) + { + VERBOSE2("OscReceiver: Got [/subscribe, " << + _handler.bool_to_string(false) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + deactivate_client(_handler, message.source().hostname(), + message.source().port()); + } + // unsubscribing client: "/subscribe, Fss, hostname, port" + else if(!message.types().compare("Fss")) + { + std::string hostname(&argv[1]->s); + std::string port(&argv[2]->s); + VERBOSE2("OscReceiver: Got [/subscribe, " << + _handler.bool_to_string(false) << ", " << hostname << ", " << port + << "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + deactivate_client(_handler, hostname, port); + } + // subscribing client: "/subscribe, Tssi, hostname, port, message_level" + else if(!message.types().compare("Tssi")) + { + std::string hostname(&argv[1]->s); + std::string port(&argv[2]->s); + VERBOSE2("OscReceiver: Got [/subscribe, " << + _handler.bool_to_string(true) << ", " << hostname << ", " << port + << ", " << argv[3]->i << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + add_client(_handler, hostname, port, + get_sane_message_level(argv[3]->i)); + } + // subscribing client: "/subscribe, Ti, message_level" + else if(!message.types().compare("Ti")) + { + VERBOSE2("OscReceiver: Got [/subscribe, " << + _handler.bool_to_string(true) << ", " << argv[1]->i << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + add_client(_handler, message.source().hostname(), + message.source().port(), get_sane_message_level(argv[1]->i)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /subscribe {F,Fss,T,Ti,Tssi}."); +} + +/** + * Adds callback handlers (for server) for update messages received from + * clients. + * This function uses C++11 lambda functions to define the behavior for every + * callback. + */ +void ssr::OscReceiver::add_update_notification_methods() +{ + // update on cpu_load: "/update/cpu_load, f, load" + _handler.server().add_method("/update/cpu_load", "f", [](lo_arg **argv, int, + lo::Message message) + { + VERBOSE3("OscReceiver: Got [/update/cpu_load, " << argv[0]->f << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/cpu_load f."); + + // update on state processing: "/update/processing/state, {F,T}, + // false|true" + _handler.server().add_method("/update/processing/state", NULL, [this](lo_arg + **argv, int, lo::Message message) + { + bool state; + (void) argv; + if(!message.types().compare("T")) + { + state = true; + } + else if(!message.types().compare("F")) + { + state = false; + } + VERBOSE3("OscReceiver: Got [/update/processing/state, " << + _handler.bool_to_string(state) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/processing/state {F,T}."); + + // update on reference orientation: "/update/reference/orientation, f, + // azimuth" + _handler.server().add_method("/update/reference/orientation", "f", [](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/reference/orientation, " << + argv[0]->f << "] from client '" << message.source().hostname() << ":" + << message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/reference/orientation f."); + + // update on reference position: "/update/reference/position, ff, x, y" + _handler.server().add_method("/update/reference/position", "ff", [](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/reference/position, " << argv[0]->f + << ", " << argv[1]->f << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/reference/position ff."); + + // update on reference offset orientation: + // "/update/reference_offset/orientation, f, azimuth" + _handler.server().add_method("/update/reference_offset/orientation", "f", + [](lo_arg **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/reference_offset/orientation, " << + argv[0]->f << "] from client '" << message.source().hostname() << ":" + << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for \ +/update/reference_offset/orientation f."); + + // update on reference offset position: "/update/reference_offset/position, + // ff, x, y" + _handler.server().add_method("/update/reference_offset/position", "ff", + [](lo_arg **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/reference_offset/position, " << + argv[0]->f << ", " << argv[1]->f << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/reference_offset/position \ +ff."); + + // update on scene amplitude reference distance: + // "update/scene/amplitude_reference_distance, f, + // amplitude_reference_distance" + _handler.server().add_method("/update/scene/amplitude_reference_distance", + "f", [](lo_arg **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/scene/amplitude_reference_distance, " + << argv[0]->f << "] from client '" << message.source().hostname() << + ":" << message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for \ +/update/scene/amplitude_reference_distance f."); + + // update on scene source auto rotation: "/update/scene/auto_rotate_sources, + // {F,T}, false|true" + _handler.server().add_method("/update/scene/auto_rotate_sources", NULL, + [this](lo_arg **argv, int, lo::Message message) + { + bool state; + (void) argv; + if(!message.types().compare("T")) + { + state = true; + } + else if(!message.types().compare("F")) + { + state = false; + } + VERBOSE2("OscReceiver: Got [/update/scene/auto_rotate_sources, " << + _handler.bool_to_string(state) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/scene/auto_rotate_sources \ +{F,T}."); + + // update on scene decay exponent: "/update/scene/decay_exponent, f, + // decay_exponent" + _handler.server().add_method("/update/scene/decay_exponent", "f", [](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/scene/decay_exponent, " << argv[0]->f + << "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/scene/decay_exponent f."); + + // update on scene master signal level: "/update/scene/master_signal_level, + // f, master_signal_level" + _handler.server().add_method("/update/scene/master_signal_level", "f", + [](lo_arg **argv, int, lo::Message message) + { + VERBOSE3("OscReceiver: Got [/update/scene/master_signal_level, " << + argv[0]->f << "] from client '" << message.source().hostname() << ":" + << message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/scene/master_signal_level \ +f."); + + // update on scene sample rate: "/update/scene/sample_rate, i, sample_rate" + _handler.server().add_method("/update/scene/sample_rate", "i", [](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/scene/sample_rate, " << argv[0]->i << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/scene/sample_rate i."); + + // update on scene volume: "/update/scene/volume, f, volume" + _handler.server().add_method("/update/scene/volume", "f", [](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/scene/volume, " << argv[0]->f << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/scene/volume f."); + + // update on deleted source: "/update/source/delete, i, id" + _handler.server().add_method("/update/source/delete", "i", [](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/source/delete, " << argv[0]->i + << "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/delete i."); + + // update on source file_channel: "/update/source/file_channel, ii, id, + // file_channel" + _handler.server().add_method("/update/source/file_channel", "ii", [](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/source/file_channel, " << argv[0]->i + << ", " << argv[1]->i << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/file_channel ii."); + + // update on source file_name_or_port_number: + // "/update/source/file_name_or_port_number, is, id, file_name_or_port_number" + _handler.server().add_method("/update/source/file_name_or_port_number", "is", + [](lo_arg **argv, int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/update/source/file_name_or_port_number, " << + argv[0]->i << ", " << name << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for \ +/update/source/file_name_or_port_number is."); + + // update on source gain: "/update/source/gain, if, id, gain" + _handler.server().add_method("/update/source/gain", "if", [](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/source/gain, " << argv[0]->i + << ", " << argv[1]->f << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/gain if."); + + // update on source file length: "/update/source/length, ii, id, length" + _handler.server().add_method("/update/source/length", "ii", [](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/source/length, " << argv[0]->i + << ", " << argv[1]->i << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/length ii."); + + // update on source signal level: "/update/source/level, if, id, level" + _handler.server().add_method("/update/source/level", "if", [](lo_arg **argv, + int, lo::Message message) + { + VERBOSE3("OscReceiver: Got [/update/source/level, " << argv[0]->i << + ", "<< argv[1]->f << "] from client '" << message.source().hostname() + << ":" << message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/level if."); + + // update on source model: "/update/source/model, is, id, model" + _handler.server().add_method("/update/source/model", "is", [](lo_arg **argv, + int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/update/source/model, " << argv[0]->i << ", " + << name << "] from client '" << message.source().hostname() << ":" << + message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/model is."); + + // update on source mute: "/update/source/mute, i{F,T}, id, false|true" + _handler.server().add_method("/update/source/mute", NULL, [this](lo_arg + **argv, int, lo::Message message) + { + if(!message.types().compare("iT")) + { + VERBOSE2("OscReceiver: Got [/update/source/mute, " << argv[0]->i << + ", " << _handler.bool_to_string(true) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + else if(!message.types().compare("iF")) + { + VERBOSE2("OscReceiver: Got [/update/source/mute, " << argv[0]->i << + ", " << _handler.bool_to_string(false) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/mute i{F,T}."); + + // update on source name: "/update/source/name, is, id, name" + _handler.server().add_method("/update/source/name", "is", [](lo_arg **argv, + int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/update/source/name, " << argv[0]->i << ", " + << name << "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/name is."); + + // update on source orientation: "/update/source/orientation, if, id, + // azimuth" + _handler.server().add_method("/update/source/orientation", "if", [](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/source/orientation, " << argv[0]->i + << ", " << argv[1]->f << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/orientation if."); + + // update on new source: "/update/source/new, i, id" + _handler.server().add_method("/update/source/new", "i", [](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/source/new, " << argv[0]->i + << "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/new i."); + + // update on source port_name: "/update/source/port_name, is, id, port_name" + _handler.server().add_method("/update/source/port_name", "is", [](lo_arg + **argv, int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/update/source/port_name, " << + argv[0]->i << ", " << name << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/port_name is."); + + // update on source position: "/update/source/position, iff, id, x, y" + _handler.server().add_method("/update/source/position", "iff", [](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/update/source/position, " << argv[0]->i << + ", " << argv[1]->f << ", " << argv[2]->f << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/position iff."); + + // update on source position fixation: "/update/source/position_fixed, + // i{F,T}, id, false|true" + _handler.server().add_method("/update/source/position_fixed", NULL, + [this](lo_arg **argv, int, lo::Message message) + { + if(!message.types().compare("iT")) + { + VERBOSE2("OscReceiver: Got [/update/source/position_fixed, " << + argv[0]->i << ", " << _handler.bool_to_string(true) << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + else if(!message.types().compare("iF")) + { + VERBOSE2("OscReceiver: Got [/update/source/position_fixed, " << + argv[0]->i << ", " << _handler.bool_to_string(false) << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/position_fixed \ +i{F,T}."); + + // update on source properties_file: "/update/source/properties_file, is, id, + // properties_file" + _handler.server().add_method("/update/source/properties_file", "is", + [](lo_arg **argv, int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/update/source/properties_file, " << + argv[0]->i << ", " << name << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/source/properties_file is."); + + // update on transport seek: "/update/transport/seek, s, time" + _handler.server().add_method("/update/transport/seek", "s", [](lo_arg **argv, + int, lo::Message message) + { + std::string seek = &argv[0]->s; + VERBOSE3("OscReceiver: Got [/update/transport/seek, " << seek << + "] from client '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/transport/seek s."); + + // update on transport state: "/update/transport/state, {F,T}, false|true" + _handler.server().add_method("/update/transport/state", NULL, [this](lo_arg + **argv, int, lo::Message message) + { + bool state; + (void) argv; + if(!message.types().compare("T")) + { + state = true; + } + else if(!message.types().compare("F")) + { + state = false; + } + VERBOSE3("OscReceiver: Got [/update/transport/state, " << + _handler.bool_to_string(state) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /update/transport/state {F,T}."); +} + +/** + * Adds callback handlers (for clients) for /poll and /message_level messages + * received from a server. + * This function uses C++11 lambda functions to define the behavior for every + * callback, that interface with the Publisher's functionality. + */ +void ssr::OscReceiver::add_server_to_client_methods() +{ + // set cpu_load: "/cpu_load, f, load" + _handler.server().add_method("/cpu_load", "f", [](lo_arg **argv, int, + lo::Message message) + { + VERBOSE3("OscReceiver: Got [/cpu_load, " << argv[0]->f << + "] from server '" << message.source().hostname() << ":" << + message.source().port() << "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /cpu_load f."); + + // set OscSender's _server _message_level: /message_level, i + _handler.server().add_method("/message_level", "i", [this](lo_arg **argv, + int, lo::Message message) + { + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + VERBOSE2("OscReceiver: Got [/message_level, " << argv[0]->i << + "] from server '" << message.source().hostname() << ":" << + message.source().port() << "'."); + if(is_server(_handler, hostname, port)) + set_server_message_level(_handler, get_sane_message_level(argv[0]->i)); + } + ); + VERBOSE("OscReceiver: Added callback for /message_level i."); + + // set _server_address for OscSender through OscHandler, depending on, if + // polled from given server before + _handler.server().add_method("/poll", NULL, [this](lo_arg **argv, int, + lo::Message message) + { + lo::Address from(message.source()); + std::string hostname(from.hostname()); + std::string port(from.port()); + (void) argv; + if(!is_server(_handler, hostname, port)) + { + VERBOSE2("OscReceiver: Got [/poll] from server " << from.hostname() << + ":" << from.port() << ". Subscribing..."); + set_server_address(_handler, hostname, port); + from.send_from(_handler.server(), "/subscribe", "T"); + } + else + { + VERBOSE2("OscReceiver: Got [/poll] from server " << from.hostname() << + ":" << from.port() << "."); + from.send_from(_handler.server(), "/alive", ""); + VERBOSE2("OscReceiver: Sent [/alive] to server " << from.hostname() << + ":" << from.port() << "."); + } + } + ); + VERBOSE("OscReceiver: Added callback for /poll."); +} + +/** + * Adds callback handlers (for clients and server) for source related messages + * received from a server or a client with MessageLevel::SERVER (respectively). + * This function uses C++11 lambda functions to define the behavior for every + * callback, that interface with the Publisher's functionality. + */ +void ssr::OscReceiver::add_source_methods() +{ + // delete source: "/source/delete, i, id" + // special case: i == 0 deletes all sources! + _handler.server().add_method("/source/delete", "i", [this](lo_arg **argv, + int, lo::Message message) + { + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + VERBOSE2("OscReceiver: Got [/source/delete, " << argv[0]->i << + "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i == 0) + { + _controller.delete_all_sources(); + } + else if (argv[0]->i > 0) + { + _controller.delete_source(argv[0]->i); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/delete i."); + + // set source file_channel: "/source/file_channel, ii, id, file_channel" + _handler.server().add_method("/source/file_channel", "ii", [this](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/source/file_channel, " << argv[0]->i << ", " + << argv[1]->i << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_file_channel(argv[0]->i, argv[1]->i); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/file_channel ii."); + + // set source file_name_or_port_number: "/source/file_name_or_port_number, + // is, id, file_name_or_port_number" + _handler.server().add_method("/source/file_name_or_port_number", "is", + [this](lo_arg **argv, int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/source/file_name_or_port_number, " << + argv[0]->i << ", " << name << "] from " << _handler.from_is() << + " '" << message.source().hostname() << ":" << message.source().port() + << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_file_name(argv[0]->i, name); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/file_name_or_port_number " + "is."); + + // set source gain: "/source/gain, if, id, gain" + _handler.server().add_method("/source/gain", "if", [this](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/source/gain, " << argv[0]->i << ", " << + argv[1]->f << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0 && argv[1]->f >= 0.0) + _controller.set_source_gain(argv[0]->i, argv[1]->f); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/gain if."); + + // set source signal level (GUI_CLIENT only): "/source/level, if, id, level" + _handler.server().add_method("/source/level", "if", [this](lo_arg **argv, + int, lo::Message message) + { + VERBOSE3("OscReceiver: Got [/source/level, " << argv[0]->i << ", "<< + argv[1]->f << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /source/level if."); + + // set source model: "/source/model, is, id, model" + _handler.server().add_method("/source/model", "is", [this](lo_arg **argv, + int, lo::Message message) + { + std::string name(&argv[1]->s); + Source::model_t model = Source::model_t(); + if (!apf::str::S2A(name, model)) + { + model = Source::point; + } + VERBOSE2("OscReceiver: Got [/source/model, " << argv[0]->i << ", " << + name << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_model(argv[0]->i, model); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/model is."); + + // set source mute: "/source/mute, i{F,T}, id, true|false" + _handler.server().add_method("/source/mute", NULL, [this](lo_arg **argv, int, + lo::Message message) + { + if(!message.types().compare("iT")) + { + VERBOSE2("OscReceiver: Got [/source/mute, " << argv[0]->i << + ", true] from " << _handler.from_is() << " '"<< + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_mute(argv[0]->i, true); + } + } + else if(!message.types().compare("iF")) + { + VERBOSE2("OscReceiver: Got [/source/mute, " << argv[0]->i << + ", false] from " << _handler.from_is() << " '"<< + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_mute(argv[0]->i, false); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/mute i{F,T}."); + + // set source name: "/source/name, is, id, name" + _handler.server().add_method("/source/name", "is", [this](lo_arg **argv, int, + lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/source/name, " << argv[0]->i << ", " << name + << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_name(argv[0]->i, name); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/name is."); + + // create new source: "/source/new, sssffff{F,T}{F,T}{F,T}, name, model, + // file_name_or_port_number, x, y, orientation, gain, position_fixed, + // orientation_fixed, muted" + // create new source: "/source/new, sssffffis{F,T}{F,T}{F,T}, name, model, + // file_name_or_port_number, x, y, orientation, gain, file_channel, + // properties_file, position_fixed, orientation_fixed, muted" + _handler.server().add_method("/source/new", NULL, [this](lo_arg **argv, int, + lo::Message message) + { + int file_channel = 0; + std::string properties_file = ""; + std::string channel_and_properties = ""; + bool position_fixed; + bool orientation_fixed; + bool muted; + bool setup = false; + if (!message.types().compare("sssffffTTT")) + { + position_fixed = true; + orientation_fixed = true; + muted = true; + setup = true; + } + if (!message.types().compare("sssffffTTF")) + { + position_fixed = true; + orientation_fixed = true; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffTFF")) + { + position_fixed = true; + orientation_fixed = false; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffFFF")) + { + position_fixed = false; + orientation_fixed = false; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffTFT")) + { + position_fixed = true; + orientation_fixed = false; + muted = true; + setup = true; + } + if (!message.types().compare("sssffffFTF")) + { + position_fixed = false; + orientation_fixed = true; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffFTT")) + { + position_fixed = false; + orientation_fixed = true; + muted = true; + setup = true; + } + if (!message.types().compare("sssffffFFT")) + { + position_fixed = false; + orientation_fixed = false; + muted = true; + setup = true; + } + if (!message.types().compare("sssffffisTTT")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = true; + orientation_fixed = true; + muted = true; + setup = true; + } + if (!message.types().compare("sssffffisTTF")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = true; + orientation_fixed = true; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffisTFF")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = true; + orientation_fixed = false; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffisFFF")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = false; + orientation_fixed = false; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffisTFT")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = true; + orientation_fixed = false; + muted = true; + setup = true; + } + if (!message.types().compare("sssffffisFTF")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = false; + orientation_fixed = true; + muted = false; + setup = true; + } + if (!message.types().compare("sssffffisFTT")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = false; + orientation_fixed = true; + muted = true; + setup = true; + } + if (!message.types().compare("sssffffisFFT")) + { + file_channel = argv[7]->i; + properties_file = &(argv[8]->s); + channel_and_properties = (file_channel+", "+properties_file); + position_fixed = false; + orientation_fixed = false; + muted = true; + setup = true; + } + if (setup) + { + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + std::string name(&(argv[0]->s)); + std::string file_name_or_port_number(&(argv[2]->s)); + float x(argv[3]->f); + float y(argv[4]->f); + float gain(argv[6]->f); + if (gain < 0.0) + gain = 0.0; + Source::model_t model = Source::model_t(); + if (!apf::str::S2A(apf::str::A2S(argv[1]->s), model)) + { + model = Source::point; + } + Position position(x, y); + Orientation orientation(argv[5]->f); + VERBOSE2("OscReceiver: Got [/source/new, " << name << ", " << model + << ", " << file_name_or_port_number << ", " << x << ", " << y << + ", " << orientation.azimuth << ", " << gain << + channel_and_properties << ", " << + _handler.bool_to_string(position_fixed) << ", " << + _handler.bool_to_string(orientation_fixed) << ", " << + _handler.bool_to_string(muted) << "] from " << + _handler.from_is() << " '" << message.source().hostname() << ":" + << message.source().port() << "'."); + _controller.new_source(name, model, file_name_or_port_number, + file_channel, position, position_fixed, orientation, + orientation_fixed, gain, muted, properties_file); + VERBOSE2("OscReceiver: Created source with following properties:" + "\nname: " << name << + "\nmodel: " << model << + "\nfile_name_or_port_number: " << file_name_or_port_number << + "\nfile_channel: " << file_channel << + "\nposition: " << position << + "\nposition_fixed: " << position_fixed << + "\norientation: " << orientation << + "\norientation_fixed: " << orientation_fixed << + "\ngain (linear): " << gain << + "\nmuted: " << muted << + "\nproperties_file: " << properties_file); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/new \ +{sssffff,sssffffis}{F,T}{F,T}{F,T}."); + + // set source orientation: "/source/orientation, if, id, azimuth" + _handler.server().add_method("/source/orientation", "if", [this](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/source/orientation, " << argv[0]->i << ", " + << argv[1]->f << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_orientation(argv[0]->i, + Orientation(argv[1]->f)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/orientation if."); + + // set source port name: "/source/port_name, is, id, port_name" + _handler.server().add_method("/source/port_name", "is", [this](lo_arg **argv, + int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/source/port_name, " << argv[0]->i << ", " << + name << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_port_name(argv[0]->i, name); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/port_name is."); + + // set source position: "/source/position, iff, id, x, y" + _handler.server().add_method("/source/position", "iff", [this](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/source/position, " << argv[0]->i << ", " << + argv[1]->f << ", " << argv[2]->f << "] from " << _handler.from_is() << + " '" << message.source().hostname() << ":" << message.source().port() + << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_position(argv[0]->i, Position(argv[1]->f, + argv[2]->f)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/position iff."); + + // set source fixed: "/source/position_fixed, i{F,T}, id, true|false" + _handler.server().add_method("/source/position_fixed", NULL, [this](lo_arg + **argv, int, lo::Message message) + { + if (!message.types().compare("iT")) + { + VERBOSE2("OscReceiver: Got [/source/position_fixed, " << argv[0]->i << + ", true] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_position_fixed(argv[0]->i, true); + } + } + else if (!message.types().compare("iF")) + { + VERBOSE2("OscReceiver: Got [/source/position_fixed, " << argv[0]->i << + ", false] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_position_fixed(argv[0]->i, false); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/position_fixed i{F,T}."); + + // set source file: "/source/properties_file, is, id, properties_file" + _handler.server().add_method("/source/properties_file", "is", [this](lo_arg + **argv, int, lo::Message message) + { + std::string name(&argv[1]->s); + VERBOSE2("OscReceiver: Got [/source/properties_file, " << argv[0]->i << + ", " << name << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + if (argv[0]->i > 0) + _controller.set_source_properties_file(argv[0]->i, name); + } + } + ); + VERBOSE("OscReceiver: Added callback for /source/properties_file is."); + +} + +/** + * Adds callback handlers (for clients and server) for reference related + * messages received from a server or a client with MessageLevel::SERVER + * (respectively). + * This function uses C++11 lambda functions to define the behavior for every + * callback, that interface with the Publisher's functionality. + */ +void ssr::OscReceiver::add_reference_methods() +{ + // set reference orientation: "/reference/orientation, f, azimuth" + _handler.server().add_method("/reference/orientation", "f", [this](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/reference/orientation, " << argv[0]->f << + "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.set_reference_orientation(Orientation(argv[0]->f)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /reference/orientation f."); + + // set reference position: "/reference/position, ff, x, y" + _handler.server().add_method("/reference/position", "ff", [this](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/reference/position, " << argv[0]->f << ", " + << argv[1]->f << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.set_reference_position(Position(argv[0]->f, argv[1]->f)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /reference/position ff."); + + // set reference offset orientation: "/reference_offset/orientation, f, + // azimuth" + _handler.server().add_method("/reference_offset/orientation", "f" , + [this](lo_arg **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/reference_offset/orientation, " << + argv[0]->f << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.set_reference_offset_orientation(Orientation(argv[0]->f)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /reference_offset/orientation f."); + + // set reference offset position: "/reference_offset/position, ff, x, y" + _handler.server().add_method("/reference_offset/position", "ff" , + [this](lo_arg **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/reference_offset/position, " << argv[0]->f + << ", " << argv[1]->f << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.set_reference_offset_position(Position(argv[0]->f, + argv[1]->f)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /reference_offset/position ff."); +} + +/** + * Adds callback handlers (for clients and server) for scene related messages + * received from a server or a client with MessageLevel::SERVER (respectively). + * This function uses C++11 lambda functions to define the behavior for every + * callback, that interface with the Publisher's functionality. + */ +void ssr::OscReceiver::add_scene_methods() +{ + //TODO: add /scene/transfer ss, host, port (_controller.get_scene_as_XML()) + //_add_master_volume(node); + //_add_transport_state(node); + //_add_reference(node); + //_add_loudspeakers(node); + //_add_sources(node); + + // set scene's amplitude reference distance: + // "/scene/amplitude_reference_distance, f, amplitude_reference_distance" + _handler.server().add_method("/scene/amplitude_reference_distance", "f", + [this](lo_arg **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/scene/amplitude_reference_distance, " << + argv[0]->f << "] from client '" << message.source().hostname() << + ":" << message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.set_amplitude_reference_distance(argv[0]->f); + } + } + ); + VERBOSE("OscReceiver: Added callback for \ +/scene/amplitude_reference_distance f."); + + // set scene source auto rotation: "/scene/auto_rotate_sources, {F,T}, + // false|true" + _handler.server().add_method("/scene/auto_rotate_sources", NULL, + [this](lo_arg **argv, int, lo::Message message) + { + (void) argv; + if(!message.types().compare("T")) + { + VERBOSE2("OscReceiver: Got [/scene/auto_rotate_sources, " << + _handler.bool_to_string(true) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + _controller.set_auto_rotation(true); + } + } + else if(!message.types().compare("F")) + { + VERBOSE2("OscReceiver: Got [/scene/auto_rotate_sources, " << + _handler.bool_to_string(false) << "] from client '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + _controller.set_auto_rotation(false); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /scene/auto_rotate_sources {F,T}."); + + // clear scene: "/scene/clear" + _handler.server().add_method("/scene/clear", NULL , [this](lo_arg **argv, + int, lo::Message message) + { + (void) argv; + VERBOSE2("OscReceiver: Got [/scene/clear] from " << _handler.from_is() << + " '" << message.source().hostname() << ":" << message.source().port() + << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.delete_all_sources(); + } + } + ); + VERBOSE("OscReceiver: Added callback for /scene/clear."); + + // set scene decay exponent: "/scene/decay_exponent, f, decay_exponent" + _handler.server().add_method("/scene/decay_exponent", "f", [this](lo_arg + **argv, int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/scene/decay_exponent, " << argv[0]->f + << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.set_decay_exponent(argv[0]->f); + } + } + ); + VERBOSE("OscReceiver: Added callback for /scene/decay_exponent f."); + + // load scene from file: "/scene/load, s, file" + _handler.server().add_method("/scene/load", "s" , [this](lo_arg **argv, int, + lo::Message message) + { + std::string name(&argv[0]->s); + VERBOSE2("OscReceiver: Got [/scene/load, " << name << "] from " << + _handler.from_is() << " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.load_scene(name); + } + } + ); + VERBOSE("OscReceiver: Added callback for /scene/load s."); + + // save scene to file: "/scene/save, s, file" + _handler.server().add_method("/scene/save", "s" , [this](lo_arg **argv, int, + lo::Message message) + { + std::string name(&argv[0]->s); + VERBOSE2("OscReceiver: Got [/scene/save, " << name << "] from " << + _handler.from_is() << " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + _controller.save_scene_as_XML(name); + } + } + ); + VERBOSE("OscReceiver: Added callback for /scene/save s."); + + // set scene master signal level: "/scene/master_signal_level, f, + // master_signal_level" + _handler.server().add_method("/scene/master_signal_level", "f", [this](lo_arg + **argv, int, lo::Message message) + { + VERBOSE3("OscReceiver: Got [/scene/master_signal_level, " << argv[0]->f + << "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + } + ); + VERBOSE("OscReceiver: Added callback for /scene/master_signal_level f."); + + // set master volume: "/scene/volume, f, volume" + _handler.server().add_method("/scene/volume", "f" , [this](lo_arg **argv, + int, lo::Message message) + { + VERBOSE2("OscReceiver: Got [/scene/volume, " << argv[0]->f << "] from " + << _handler.from_is() << " '" << message.source().hostname() << ":" + << message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.set_master_volume(apf::math::dB2linear(argv[0]->f)); + } + } + ); + VERBOSE("OscReceiver: Added callback for /scene/volume f."); +} + +/** + * Adds callback handlers (for clients and server) for processing related + * messages received from a server or a client with MessageLevel::SERVER + * (respectively). + * This function uses C++11 lambda functions to define the behavior for every + * callback, that interface with the Publisher's functionality. + */ +void ssr::OscReceiver::add_processing_methods() +{ + // set processing state: "/processing/state, T, true" + _handler.server().add_method("/processing/state", NULL, [this](lo_arg **argv, + int, lo::Message message) + { + (void) argv; + if(!message.types().compare("T")) + { + VERBOSE2("OscReceiver: Got [/processing/state, " << + _handler.bool_to_string(true) << "] from " << _handler.from_is() << + " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + _controller.start_processing(); + } + } + else if(!message.types().compare("F")) + { + VERBOSE2("OscReceiver: Got [/processing/state, " << + _handler.bool_to_string(false) << "] from " << _handler.from_is() + << " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + _controller.stop_processing(); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /processing/state {F,T}."); + +} + +/** + * Adds callback handlers (for clients and server) for transport related + * messages received from a server or a client with MessageLevel::SERVER + * (respectively). + * This function uses C++11 lambda functions to define the behavior for every + * callback, that interface with the Publisher's functionality. + */ +void ssr::OscReceiver::add_transport_methods() +{ + // rewind transport state: "/transport/rewind" + _handler.server().add_method("/transport/rewind", NULL , [this](lo_arg + **argv, int, lo::Message message) + { + (void) argv; + VERBOSE2("OscReceiver: Got [/transport/rewind] from " << + _handler.from_is() << " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.transport_locate(0); + } + } + ); + VERBOSE("OscReceiver: Added callback for /transport/rewind."); + + // seek transport state: "/transport/seek, s, time" + _handler.server().add_method("/transport/seek", "s" , [this](lo_arg **argv, + int, lo::Message message) + { + std::string message_time(&argv[0]->s); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + VERBOSE3("OscReceiver: Got [/transport/seek, " << message_time << + "] from " << _handler.from_is() << " '" << + message.source().hostname() << ":" << message.source().port() << + "'."); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + float time; + if(apf::str::string2time(message_time, time)) + { + _controller.transport_locate(time); + } + else + { + ERROR("Couldn't get the time out of the \"seek\" attribute (\"" + << message_time << "\")."); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /transport/seek s."); + + // set transport state: "transport/state, T, true" + _handler.server().add_method("/transport/state", "T" , [this](lo_arg **argv, + int, lo::Message message) + { + (void) argv; + if(!message.types().compare("T")) + { + VERBOSE3("OscReceiver: Got [/transport/state, " << + _handler.bool_to_string(true) << "] from " << _handler.from_is() << + " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + _controller.transport_start(); + } + } + if(!message.types().compare("F")) + { + VERBOSE3("OscReceiver: Got [/transport/state, " << + _handler.bool_to_string(false) << "] from " << _handler.from_is() + << " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, + hostname, port, + MessageLevel::SERVER))) + { + _controller.transport_stop(); + } + } + } + ); + VERBOSE("OscReceiver: Added callback for /transport/state {F,T}."); +} + +/** + * Adds callback handlers (for clients and server) for tracker related messages + * received from a server or a client with MessageLevel::SERVER (respectively). + * This function uses C++11 lambda functions to define the behavior for every + * callback, that interface with the Publisher's functionality. + */ +void ssr::OscReceiver::add_tracker_methods() +{ + // reset tracker: "/tracker/reset" + _handler.server().add_method("/tracker/reset", NULL , [this](lo_arg **argv, + int, lo::Message message) + { + (void) argv; + VERBOSE2("OscReceiver: Got [/tracker/reset] from " << _handler.from_is() + << " '" << message.source().hostname() << ":" << + message.source().port() << "'."); + std::string hostname(message.source().hostname()); + std::string port(message.source().port()); + if ((_handler.is_client() && is_server(_handler, hostname, port)) || + (_handler.is_server() && client_has_message_level(_handler, hostname, + port, + MessageLevel::SERVER))) + { + _controller.calibrate_client(); + } + } + ); + VERBOSE("OscReceiver: Added callback for /tracker/reset."); +} + +/** + * Creates a sane MessageLevel from an int32_t + * @param message_level An int32_t + * @return a MessageLevel + */ +ssr::MessageLevel ssr::OscReceiver::get_sane_message_level(int32_t message_level) +{ + if(message_level <= 0){ + return ssr::MessageLevel::CLIENT; + } + else if(ssr::MessageLevel::MAX_VALUE < + static_cast(message_level)) + { + return ssr::MessageLevel::GUI_SERVER; + } + else + { + return static_cast(message_level); + } +} diff --git a/src/networking/oscreceiver.h b/src/networking/oscreceiver.h new file mode 100644 index 00000000..c050e0ca --- /dev/null +++ b/src/networking/oscreceiver.h @@ -0,0 +1,76 @@ +/** + * @file oscreceiver.h + * Header for OscReceiver, declaring a class, responsible for evaluating + * received OSC messages and interfacing the SSR's controller. + */ + +#ifndef OSC_RECEIVER_H +#define OSC_RECEIVER_H + + +#ifdef HAVE_CONFIG_H +#include // for ENABLE_* +#endif + +#include +#include + +#include "ssr_global.h" // for VERBOSE, MessageLevel + +namespace ssr +{ + +struct Publisher; + +// forward declaration for OscHandler friend functions +class OscHandler; + +/** + * OscReceiver + * This class holds a Publisher and an OscHandler reference. + * It is responsible for all incoming OSC messages and using callback functions + * to trigger functionality in the Publisher. + */ +class OscReceiver +{ + private: + Publisher& _controller; + ssr::OscHandler& _handler; + void add_client_to_server_methods(); + void add_update_notification_methods(); + void add_server_to_client_methods(); + void add_source_methods(); + void add_reference_methods(); + void add_scene_methods(); + void add_processing_methods(); + void add_transport_methods(); + void add_tracker_methods(); + ssr::MessageLevel get_sane_message_level(int32_t message_level); + + public: + OscReceiver(Publisher& controller, OscHandler& handler); + ~OscReceiver(); + void start(); + void stop(); + void set_server_address(OscHandler& handler, std::string& hostname, + std::string& port); + lo::Address server_address(OscHandler& handler); + void set_server_message_level(OscHandler& handler, MessageLevel + message_level); + bool server_is_default(OscHandler& handler); + bool is_server(OscHandler& handler, std::string& hostname, std::string& + port); + void add_client(OscHandler& self, std::string hostname, std::string port, + ssr::MessageLevel message_level); + void deactivate_client(OscHandler& self, std::string hostname, std::string + port); + void set_client_message_level(OscHandler& self, std::string hostname, + std::string port, ssr::MessageLevel message_level); + bool client_has_message_level(OscHandler& self, std::string& hostname, + std::string& port, ssr::MessageLevel message_level); + void increment_client_alive_counter(OscHandler& self, std::string hostname, + std::string port); +}; + +} // namespace ssr +#endif diff --git a/src/networking/oscsender.cpp b/src/networking/oscsender.cpp new file mode 100644 index 00000000..412f3b14 --- /dev/null +++ b/src/networking/oscsender.cpp @@ -0,0 +1,1679 @@ +/** + * Implementation of oscsender.h + * @file oscsender.cpp + */ + +#include +#include "oschandler.h" +#include "oscsender.h" +#include "publisher.h" +#include "apf/stringtools.h" +#include "apf/math.h" + +/** + * Constructor used to create OscSender objects + * @param controller reference to a Publisher object + * @param handler reference to an OscHandler object + */ +ssr::OscSender::OscSender(Publisher& controller, OscHandler& handler) + : _controller(controller) + , _handler(handler) + , _server("none", "50001", MessageLevel::SERVER) +{ + VERBOSE("OscSender: Initialized."); +} + +/** + * Destructor + */ +ssr::OscSender::~OscSender() +{} + +/** + * Function to start the OscSender object + * This subscribes the OscSender to the Publisher and starts the + * lo::ServerThread to send from + */ +void ssr::OscSender::start() +{ + _controller.subscribe(this); + _is_subscribed = true; + if (_handler.is_server()) + { + _poll_all_clients = true; + std::thread _poll_thread(&OscSender::poll_all_clients, this); + _poll_thread.detach(); + } +} + +/** + * Function to stop the OscSender object + * This unsubscribes the OscSender from the Publisher and stops the client + * polling thread + */ +void ssr::OscSender::stop() +{ + _controller.unsubscribe(this); + _is_subscribed = false; + if (_handler.is_server()) + { + _poll_all_clients = false; + std::this_thread::sleep_for(std::chrono::milliseconds(_poll_milliseconds)); + remove_all_clients(); + } +} + +/** + * Returns true, if the _server.address() is the default (setup at initialization) + * @return true, if _server.address() is the default, false otherwise. + */ +bool ssr::OscSender::server_is_default() +{ + if((_server.hostname().compare("none") == 0) && + (_server.port().compare("50001") == 0)) + { + return true; + } + else + { + return false; + } +} + +/** + * Returns true, if the _server.address()'s hostname and port are the same as + * the provided. + * @param hostname a reference to a std::string representing the hostname + * @param port a reference to a std::string representing the port + * @return true, if _server.address() has the same hostname and port, false + * otherwise. + */ +bool ssr::OscSender::is_server(std::string& hostname, std::string& port) +{ + if((_server.hostname().compare(hostname) == 0) && + (_server.port().compare(port) == 0)) + { + return true; + } + else + { + return false; + } +} + +/** + * Sends a '/poll' message to all client instances listed in _clients, then + * makes the thread calling this function sleep for 1000 milliseconds + */ +void ssr::OscSender::poll_all_clients() +{ + VERBOSE("OscSender: Starting to poll all clients."); + while(_poll_all_clients) + { + for(const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/poll", ""); + client->decrement_alive_counter(); + } + } + //TODO find better solution to compensate for execution time + std::this_thread::sleep_for(std::chrono::milliseconds(_poll_milliseconds)); + } + VERBOSE2("OscSender: Stopped polling all clients."); +} + +/** + * Function to return OscSender's _server.address() + * @return a lo::Address object representing the current server for this client + */ +lo::Address& ssr::OscSender::server_address() +{ + return _server.address(); +} + +/** + * Function to set OscSender server's _message_level (client). + * @param MessageLevel enum representing the new message level + */ +void ssr::OscSender::set_server_message_level(MessageLevel message_level) +{ + _server.set_message_level(message_level); +} + +/** + * Function to set OscSender's _server address + * @param hostname a std::string& to be used as hostname + * @param port a std::string& to be used as port + */ +void ssr::OscSender::set_server_address(std::string& hostname, std::string& port) +{ + _server.set_address(hostname, port); + VERBOSE2("OscSender: Setting up new server address: "<< + _server.hostname() << ":" << _server.port() << "."); +} + +/** + * Checks keys of _new_sources map against provided source id + * @return true, if source id is found in _new_sources, false otherwise + */ +bool ssr::OscSender::is_new_source(id_t id) +{ + if (_new_sources.empty()) + return false; + //TODO: introduce exception handling for this call + if(_new_sources.find(id) != _new_sources.end()) + { + return true; + } + else + { + return false; + } +} + +/** + * Checks, whether the settings stored in _new_sources under given source id + * are sufficient to send a '/source/new' message to all clients + * @return true, if sufficient fields are available, false otherwise + */ +bool ssr::OscSender::is_complete_source(id_t id) +{ + bool is_complete = false; + if(is_new_source(id)) + { + if((_new_sources.at(id).has_key("name") && + _new_sources.at(id).has_key("model") && + _new_sources.at(id).has_key("file_name_or_port_number") && + _new_sources.at(id).has_key("x") && + _new_sources.at(id).has_key("y") && + _new_sources.at(id).has_key("orientation") && + _new_sources.at(id).has_key("gain") && + _new_sources.at(id).has_key("file_channel") && + _new_sources.at(id).has_key("properties_file") && + _new_sources.at(id).has_key("position_fixed") && + _new_sources.at(id).has_key("orientation_fixed") && + _new_sources.at(id).has_key("mute") && + _new_sources.at(id).size() == 12)|| + (_new_sources.at(id).has_key("name") && + _new_sources.at(id).has_key("model") && + _new_sources.at(id).has_key("file_name_or_port_number") && + _new_sources.at(id).has_key("x") && + _new_sources.at(id).has_key("y") && + _new_sources.at(id).has_key("orientation") && + _new_sources.at(id).has_key("gain") && + _new_sources.at(id).has_key("position_fixed") && + _new_sources.at(id).has_key("orientation_fixed") && + _new_sources.at(id).has_key("mute") && + _new_sources.at(id).size() == 10)) + is_complete = true; + } + return is_complete; +} + +/** + * Creates a message used to create a new source on clients. It will + * collect all parameters from a parameter_map in _new_sources according to an + * id. + * @param id id_t id of the local source a message will be created for. + */ +void ssr::OscSender::send_new_source_message_from_id(id_t id) +{ + if(_new_sources.at(id).has_key("name") && + _new_sources.at(id).has_key("model") && + _new_sources.at(id).has_key("file_name_or_port_number") && + _new_sources.at(id).has_key("x") && + _new_sources.at(id).has_key("y") && + _new_sources.at(id).has_key("orientation") && + _new_sources.at(id).has_key("gain") && + _new_sources.at(id).has_key("file_channel") && + _new_sources.at(id).has_key("properties_file") && + _new_sources.at(id).has_key("position_fixed") && + _new_sources.at(id).has_key("orientation_fixed") && + _new_sources.at(id).has_key("mute")) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/new", + "sssffffis"+ + _handler.bool_to_message_type(_new_sources.at(id).get( + "position_fixed", false)) + +_handler.bool_to_message_type(_new_sources.at(id).get( + "orientation_fixed", false)) + +_handler.bool_to_message_type(_new_sources.at(id).get( + "mute", false)), + _new_sources.at(id).get("name", "").c_str(), + _new_sources.at(id).get("model", "").c_str(), + _new_sources.at(id).get( + "file_name_or_port_number","").c_str(), + _new_sources.at(id).get("x", 0.0), + _new_sources.at(id).get("y", 0.0), + _new_sources.at(id).get("orientation", 0.0), + _new_sources.at(id).get("gain", 0.0), + _new_sources.at(id).get("file_channel", 1), + _new_sources.at(id).get("properties_file", "").c_str()); + VERBOSE2("OscSender: Sent [/source/new, sssffffis" << + _handler.bool_to_message_type(_new_sources.at(id).get( + "position_fixed", false)) << + _handler.bool_to_message_type(_new_sources.at(id).get( + "orientation_fixed", false)) << + _handler.bool_to_message_type(_new_sources.at(id).get( + "mute", false)) << ", " << + _new_sources.at(id).get("name", "") << ", " << + _new_sources.at(id).get("model", "") << ", " << + _new_sources.at(id).get("file_name_or_port_number","") + << ", " << + _new_sources.at(id).get("x", 0.0) << ", " << + _new_sources.at(id).get("y", 0.0) << ", " << + _new_sources.at(id).get("orientation", 0.0) << ", " << + _new_sources.at(id).get("gain", 0.0) << ", " << + _new_sources.at(id).get("file_channel", 1) << ", " << + _new_sources.at(id).get("properties_file", "") + << "] to client '" << + client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + _new_sources.erase(id); + } + else if(_new_sources.at(id).has_key("name") && + _new_sources.at(id).has_key("model") && + _new_sources.at(id).has_key("file_name_or_port_number") && + _new_sources.at(id).has_key("x") && + _new_sources.at(id).has_key("y") && + _new_sources.at(id).has_key("orientation") && + _new_sources.at(id).has_key("gain") && + _new_sources.at(id).has_key("position_fixed") && + _new_sources.at(id).has_key("orientation_fixed") && + _new_sources.at(id).has_key("mute")) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/new", + "sssffff"+ + _handler.bool_to_message_type( + _new_sources.at(id).get("position_fixed", false)) + +_handler.bool_to_message_type( + _new_sources.at(id).get("orientation_fixed", false)) + +_handler.bool_to_message_type(_new_sources.at(id).get( + "mute", false)), + _new_sources.at(id).get("name", "").c_str(), + _new_sources.at(id).get("model", "").c_str(), + _new_sources.at(id).get( + "file_name_or_port_number","").c_str(), + _new_sources.at(id).get("x", 0.0), + _new_sources.at(id).get("y", 0.0), + _new_sources.at(id).get("orientation", 0.0), + _new_sources.at(id).get("gain", 0.0)); + VERBOSE2("OscSender: Sent [/source/new, sssffff" << + _handler.bool_to_message_type( + _new_sources.at(id).get("position_fixed", false)) << + _handler.bool_to_message_type( + _new_sources.at(id).get("orientation_fixed", false)) << + _handler.bool_to_message_type( + _new_sources.at(id).get("mute", false)) << ", " << + _new_sources.at(id).get("name", "") << ", " << + _new_sources.at(id).get("model", "") << ", " << + _new_sources.at(id).get("file_name_or_port_number","") + << ", " << + _new_sources.at(id).get("x", 0.0) << ", " << + _new_sources.at(id).get("y", 0.0) << ", " << + _new_sources.at(id).get("orientation", 0.0) << ", " << + _new_sources.at(id).get("gain", 0.0) + << "] to client '" << + client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + _new_sources.erase(id); + } +} + +/** + * Adds a new client to the vector of clients + * @param hostname std::string representing the hostname of a client + * @param port std::string representing the port of a client + */ +void ssr::OscSender::add_client(std::string hostname, std::string port, + ssr::MessageLevel message_level) +{ + bool setup = false; + for (auto& client: _clients) + { + if(client && !(client->active()) && + !(client->address().hostname().compare(hostname)) && + !(client->address().port().compare(port)) ) + { + if(client->message_level() != message_level) + client->set_message_level(message_level); + client->activate(); + client->reset_alive_counter(); + setup = true; + VERBOSE2("OscSender: Recycled client " << hostname << ":" << port << + "."); + break; + } + else if(client && client->active() && + !(client->address().hostname().compare(hostname)) && + !(client->address().port().compare(port)) ) + { + setup = true; + VERBOSE2("OscSender: Client " << hostname << ":" << port << + " already active."); + } + } + if (!setup) + { + _clients.push_back(new OscClient(hostname, port, message_level)); + VERBOSE2("OscSender: Added new client '" << hostname << ":" << port << + "' using message level " << static_cast(message_level) << "."); + } +} + +/** + * Deactivate a client + * @param hostname std::string representing the hostname of a client + * @param port std::string representing the port of a client + */ +void ssr::OscSender::deactivate_client(std::string hostname, std::string port) +{ + for (auto& client: _clients) + { + if(!(client->hostname().compare(hostname)) && + !(client->port().compare(port)) && client->active()) + { + client->deactivate(); + VERBOSE2("OscSender: Deactivated client " << hostname << ":" << port << + "."); + } + } +} + +/** + * Set MessageLevel of a client + * @param hostname std::string representing the hostname of a client + * @param port std::string representing the port of a client + * @param message_level ssr::MessageLevel enum representing the message level + * to use + */ +void ssr::OscSender::set_client_message_level(std::string& hostname, + std::string& port, ssr::MessageLevel message_level) +{ + for (auto& client: _clients) + { + if(!(client->hostname().compare(hostname)) && + !(client->port().compare(port))) + { + client->set_message_level(message_level); + VERBOSE2("OscSender: Set message level of client '" << hostname << ":" << + port << "' to: " << static_cast(message_level) << "."); + } + } +} + +/** + * Increment the _alive_counter of an active client + * @param hostname std::string representing the hostname of a client + * @param port std::string representing the port of a client + */ +void ssr::OscSender::increment_client_alive_counter(std::string& hostname, + std::string& port) +{ + for (auto& client: _clients) + { + if(!(client->hostname().compare(hostname)) && + !(client->port().compare(port)) && client->active()) + { + client->increment_alive_counter(); + break; + } + } +} + +/** + * Compare MessageLevel of a client with one provided + * @param hostname std::string representing the hostname of a client + * @param port std::string representing the port of a client + * @param message_level ssr::MessageLevel enum representing the message level + * to use + * @return true, if @s message_level is equal or less of what the client, + * defined by @s hostname and @s port has set, false otherwise (also if client + * is not found). + */ +bool ssr::OscSender::client_has_message_level(std::string& hostname, + std::string& port, ssr::MessageLevel message_level) +{ + bool has_level = false; + for (auto& client: _clients) + { + if(!(client->hostname().compare(hostname)) && + !(client->port().compare(port)) && client->active() && + client->message_level() >= message_level) + { + has_level = true; + break; + } + } + return has_level; +} + + +/** + * Removes all clients from the vector of clients. + */ +void ssr::OscSender::remove_all_clients() +{ + for (auto client: _clients) delete client; + _clients.clear(); + VERBOSE2("OscSender: Removed all clients."); +} + + +// Subscriber interface (differentiating between client and server) + + +/** + * Subscriber function called, when Publisher sets up the list of loudspeakers. + * Not implemented in OscSender. + * @param loudpspeakers Loudspeaker container representing the list of + * loudspeakers to set up. + */ +void ssr::OscSender::set_loudspeakers(const Loudspeaker::container_t& + loudspeakers) +{ + (void) loudspeakers; + VERBOSE3("OscSender: set_loudspeakers"); +} + +/** + * Subscriber function called, when Publisher created a new source. + * On server: Creates new parameter_map in _new_sources if not present + * On client: Sends message about successful creation of source to server + * @param id id_t representing the source + */ +void ssr::OscSender::new_source(id_t id) +{ + if(_handler.is_server()) + { + if(!is_new_source(id)) + _new_sources.insert(make_pair(id, apf::parameter_map())); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else if(_handler.is_client()) + { + int32_t message_id = static_cast(id); + _server.address().send_from(_handler.server(), "/update/source/new", "i", + message_id); + VERBOSE3("OscSender: Sent [/update/source/new, i, " << message_id << + "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher deleted a source. + * On server: Sends out OSC message all clients to delete source with given id. + * On client: Sends out OSC message about successful deletion of source with id + * to server and erases complementing gain level from _source_levels. + * @param id id_t representing the source + */ +void ssr::OscSender::delete_source(id_t id) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/delete", "i", + message_id); + VERBOSE3("OscSender: Sent [/source/delete, i," << message_id << + "] to client '" << client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _source_levels.erase(id); + _server.address().send_from(_handler.server(), "/update/source/delete", + "i", message_id); + VERBOSE3("OscSender: Sent [/update/source/delete, i, " << message_id << + "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher deleted all sources. + * On server: Sends out OSC message to delete all sources on all clients. + * On client: Sends out OSC message about successful deletion to server. + * Clears local _source_levels. + */ +void ssr::OscSender::delete_all_sources() +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/delete", "i", + 0); + VERBOSE3("OscSender: Sent [/source/delete, i, 0] to client '" << + client->address().hostname() << ":" << client->address().port() << + "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/delete", + "i", 0); + VERBOSE3("OscSender: Sent [/update/source/delete, i, 0] to server '" << + _server.hostname() << ":" << _server.port() << "'."); + } + _source_levels.clear(); +} + +/** + * Subscriber function called, when Publisher set a source's position. + * On server: Sends out OSC message to set position of given source on all + * clients. If id is found in _new_sources, the Position will be stored in the + * parameter_map for id and an OSC message will be send to clients only, if the + * source is complete. + * On client: Sends out OSC message about successful positioning of source to + * server. + * @param id id_t representing the source + * @param position new Position of source + * @return true + */ +bool ssr::OscSender::set_source_position(id_t id, const Position& position) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + _new_sources.at(id).set("x", position.x); + _new_sources.at(id).set("y", position.y); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/position", + "iff", message_id, position.x, position.y); + VERBOSE3("OscSender: Sent [/source/position, iff, " << message_id << + position.x << ", " << position.y << "] to client '" << + client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/position", + "iff", message_id, position.x, position.y); + VERBOSE3("OscSender: Sent [/update/source/position, iff, " << message_id << + ", " << position.x << ", " << position.y << "] to server '" << + _server.hostname() << ":" << _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's position fixed + * state. + * On server: Sends out OSC message to set position fixed state of given source + * on all clients. If id is found in _new_sources, the position_fixed state + * will be stored in the parameter_map for id and an OSC message will be send + * to clients only, if the source is complete. + * On client: Sends out OSC message about the source's position_fixed state to + * server. + * @param id id_t representing the source + * @param fixed bool representing the source's position_fixed state + * @return true + */ +bool ssr::OscSender::set_source_position_fixed(id_t id, const bool& fixed) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + _new_sources.at(id).set("position_fixed", fixed); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/source/position_fixed", + "i"+_handler.bool_to_message_type(fixed), message_id); + VERBOSE3("OscSender: Sent [/source/position_fixed, i" << + _handler.bool_to_message_type(fixed) << ", " << message_id << + "] to client '" << client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/source/position_fixed", + "i"+_handler.bool_to_message_type(fixed), message_id); + VERBOSE3("OscSender: Sent [/update/source/position_fixed, i" + +_handler.bool_to_message_type(fixed) << ", " << message_id << + "] to server '" << _server.hostname() << ":" << _server.port() << + "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's orientation. + * On server: Sends out OSC message to set Orientation of given source on all + * clients. If id is found in _new_sources, the Orientation will be stored in the + * parameter_map for id and an OSC message will be send to clients only, if the + * source is complete. + * On client: Sends out OSC message about new orientation of source to server. + * @param id id_t representing the source + * @param orientation new Orientation of source + * @return true + */ +bool ssr::OscSender::set_source_orientation(id_t id , const Orientation& + orientation) +{ + int32_t message_id = static_cast(id); + float message_orientation = orientation.azimuth; + if(_handler.is_server()) + { + if(is_new_source(id)) + { + _new_sources.at(id).set("orientation", orientation.azimuth); + _new_sources.at(id).set("orientation_fixed", false); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/orientation", + "if", message_id, message_orientation); + VERBOSE3("OscSender: Sent [/source/orientation, if, " << message_id + << ", " << message_orientation << "] to client '" << + client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/source/orientation", "if", message_id, message_orientation); + VERBOSE3("OscSender: Sent [/update/source/orientation, if, " << + apf::str::A2S(message_id) << ", " << apf::str::A2S(message_orientation) + << "] to server '" << _server.hostname() << ":" << _server.port() << + "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's gain. + * On server: Sends out OSC message to set gain of given source on all + * clients. If id is found in _new_sources, the gain will be stored in the + * parameter_map for id and an OSC message will be send to clients only, if the + * source is complete. + * On client: Sends out OSC to server message about the successful updating of + * the source's gain. + * @param id id_t representing the source + * @param gain new gain of source + * @return true + */ +bool ssr::OscSender::set_source_gain(id_t id, const float& gain) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + _new_sources.at(id).set("gain", gain); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/gain", "if", + message_id, gain); + VERBOSE3("OscSender: Sent [/source/gain, if, " << message_id << ", " + << gain << "] to client '" << client->address().hostname() << ":" + << client->address().port() << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/gain", "if", + message_id, gain); + VERBOSE3("OscSender: Sent [/update/source/gain, if, " << + apf::str::A2S(message_id) << ", " << apf::str::A2S(gain) << + "] to server '" << _server.hostname() << ":" << _server.port() << + "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's mute state. + * On server: Sends out OSC message to set mute state of given source on all + * clients. If id is found in _new_sources, the mute state will be stored in + * the parameter_map for id and an OSC message will be send to clients only, if + * the source is complete. + * On client: Sends out OSC to server message about the successful updating of + * the source's mute state. + * @param id id_t representing the source + * @param mute a bool representing the mute state of source + * @return true + */ +bool ssr::OscSender::set_source_mute(id_t id, const bool& mute) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + if(_new_sources.at(id).has_key("file_name_or_port_number")) + _new_sources.at(id).set("mute", mute); + + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/mute", + "i"+_handler.bool_to_message_type(mute), message_id); + VERBOSE3("OscSender: Sent [/source/mute, i" << + _handler.bool_to_message_type(mute) << ", " << message_id << + "] to client '" << client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/mute", + "i"+_handler.bool_to_message_type(mute), message_id); + VERBOSE3("OscSender: Sent [/update/source/mute, i" << + _handler.bool_to_message_type(mute) << ", " << + apf::str::A2S(message_id) << "] to server '" << _server.hostname() << + ":" << _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's name. + * On server: Sends out OSC message to set the name of given source on all + * clients. If id is found in _new_sources, the name will be stored in + * the parameter_map for id and an OSC message will be send to clients only, if + * the source is complete. + * On client: Sends out OSC to server message about the successful updating of + * the source's name. + * @param id id_t representing the source + * @param name a std::string representing the name of source + * @return true + */ +bool ssr::OscSender::set_source_name(id_t id, const std::string& name) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + _new_sources.at(id).set("name", name); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/name", "is", + message_id, name.c_str()); + VERBOSE3("OscSender: Sent [/source/name, is, " << message_id << ", " + << name << "] to client '" << client->address().hostname() << ":" + << client->address().port() << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/name", "is", + message_id, name.c_str()); + VERBOSE3("OscSender: Sent [/update/source/name, is, " << message_id << ", " + << name << "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's properties_file. + * On server: Sends out OSC message to set the properties_file of given source + * on all clients. If id is found in _new_sources, the properties_file will be + * stored in the parameter_map for id and an OSC message will be send to + * clients only, if the source is complete. + * On client: Sends out OSC message to server about the successful updating of + * the source's properties_file. + * @param id id_t representing the source + * @param name a std::string representing the properties_file of source + * @return true + */ +bool ssr::OscSender::set_source_properties_file(id_t id, const std::string& + name) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + if(name.compare("") && _new_sources.at(id).has_key("file_channel")) + _new_sources.at(id).set("properties_file", name); + + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/source/properties_file", "is", message_id, name.c_str()); + VERBOSE3("OscSender: Sent [/source/properties_file, is, " << + message_id << ", " << name << "] to client '" << + client->address().hostname() << ":" << client->address().port() + << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/source/properties_file", "is", message_id, name.c_str()); + VERBOSE3("OscSender: Sent [/update/source/properties_file, is, " << + message_id << ", " << name << "] to server '" << _server.hostname() << + ":" << _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set the decay exponent. + * On server: Sends out OSC message to set the decay_exponent on all clients. + * On client: Sends out OSC message to server about the updating of decay_exponent + * @param exponentn float representing the decay exponent + * @param name a std::string representing the properties_file of source + */ +void ssr::OscSender::set_decay_exponent(float exponent) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/scene/decay_exponent", + "f", exponent); + VERBOSE3("OscSender: Sent [/scene/decay_exponent, f, " << exponent << + "] to client '" << client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/scene/decay_exponent", "f", exponent); + VERBOSE3("OscSender: Sent [/update/scene/decay_exponent, f, " << exponent + << "] to server '" << _server.hostname() << ":" << _server.port() << + "'."); + } +} + +/** + * Subscriber function called, when Publisher set the amplitude reference + * distance. + * On server: Sends out OSC message to set the amplitude reference distance on + * all clients. + * On client: Sends out OSC message to server about the updating of the + * amplitude reference distance. + * @param distance a float representing the amplitude reference distance + */ +void ssr::OscSender::set_amplitude_reference_distance(float distance) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/scene/amplitude_reference_distance", "f", distance); + VERBOSE3("OscSender: Sent [/scene/amplitude_reference_distance, f, " << + distance << "] to client '" << client->address().hostname() << ":" + << client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/scene/amplitude_reference_distance", "f", distance); + VERBOSE3("OscSender: Sent [/update/scene/amplitude_reference_distance, f, " + << distance << "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set a source's model. + * On server: Sends out OSC message to set the model of given source on all + * clients. If id is found in _new_sources, the model will be stored in the + * parameter_map for id and an OSC message will be send to clients only, if the + * source is complete. + * On client: Sends out OSC message to server about the successful updating of + * the source's model. + * @param id id_t representing the source + * @param model a model_t representing the model of source + * @return true + */ +bool ssr::OscSender::set_source_model(id_t id, const Source::model_t& model) +{ + int32_t message_id = static_cast(id); + std::string message_model = apf::str::A2S(model); + if (message_model == "") return false; + if(_handler.is_server()) + { + if(is_new_source(id)) + { + _new_sources.at(id).set("model", message_model); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/source/model", "is", + message_id, message_model.c_str()); + VERBOSE3("OscSender: Sent [/source/model, is, " << message_id << ", " + << message_model << "] to client '" << + client->address().hostname() << ":" << client->address().port() + << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/model", + "is", message_id, message_model.c_str()); + VERBOSE3("OscSender: Sent [/update/source/model, is, " << message_id << + ", " << message_model << "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's port_name. On + * server: Does nothing, as port_name is local and depends on prefix + * On client: Sends out OSC message to server about the successful updating of + * the source's port_name. + * @param id id_t representing the source + * @param port_name a std::string representing the port_name of source + * @return true + * @todo check if it has to be ignored during _controller.new_source() + * @see Controller::new_source() + */ +bool ssr::OscSender::set_source_port_name(id_t id, const std::string& + port_name) +{ + int32_t message_id = static_cast(id); + if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/port_name", + "is", message_id, port_name.c_str()); + VERBOSE3("OscSender: Sent [/update/source/port_name, is, " << message_id << + ", " << port_name << "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's + * file_name_or_port_number member. + * On server: Sends out OSC message to set the file_name_or_port_number of + * given source on all clients. If id is found in _new_sources, the + * file_name_or_port_number will be stored in the parameter_map for id and an + * OSC message will be send to clients only, if the source is complete. + * On client: Sends out OSC message to server about the updating of the + * source's file_name_or_port_number. + * @param id id_t representing the source + * @param file_name a std::string representing the file name or + * port number of source + * @return true + */ +bool ssr::OscSender::set_source_file_name(id_t id, const std::string& + file_name) +{ + int32_t message_id = static_cast(id); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + _new_sources.at(id).set("file_name_or_port_number", + file_name); + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/source/file_name_or_port_number", "is", message_id, + file_name.c_str()); + VERBOSE3("OscSender: Sent [/source/file_name_or_port_number, is, " << + message_id << ", " << file_name << "] to client '" << + client->address().hostname() << ":" << client->address().port() + << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/source/file_name_or_port_number", "is", message_id, + file_name.c_str()); + VERBOSE3("OscSender: Sent [/update/source/file_name_or_port_number, is, " + << message_id << ", " << file_name << "] to server '" << + _server.hostname() << ":" << _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's channel. + * On server: Sends out OSC message to set the channel of given source on all + * clients. If id is found in _new_sources, the channel will be stored in the + * parameter_map for id and an OSC message will be send to clients only, if the + * source is complete. + * On client: Sends out OSC message to server about the updating of the + * source's channel. + * @param id id_t representing the source + * @param file_channel an int representing the channel in use for source + * @return true + */ +bool ssr::OscSender::set_source_file_channel(id_t id, const int& file_channel) +{ + int32_t message_id = static_cast(id); + int32_t message_file_channel = static_cast(file_channel); + if(_handler.is_server()) + { + if(is_new_source(id)) + { + if(file_channel > 0) + _new_sources.at(id).set("file_channel", file_channel); + + if(is_complete_source(id)) + send_new_source_message_from_id(id); + } + else + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/source/file_channel", "ii", message_id, message_file_channel); + VERBOSE3("OscSender: Sent [/source/file_channel, ii, " << message_id + << ", " << message_file_channel << "] to client '" << + client->address().hostname() << ":" << client->address().port() + << "'."); + } + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/source/file_channel", "ii", message_id, message_file_channel); + VERBOSE3("OscSender: Sent [/update/source/file_channel, ii, " << message_id + << ", " << message_file_channel << "] to server '" << _server.hostname() + << ":" << _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set a source's file length. + * On server: Does nothing, as the Publisher only emits this on creation of a + * new source. + * On client: Sends out OSC message to server about the updating of the + * source's file length. + * @param id id_t representing the source + * @param length an int representing the source file's length + * @return true + */ +bool ssr::OscSender::set_source_file_length(id_t id, const long int& length) +{ + int32_t message_id = static_cast(id); + int32_t message_length = static_cast(length); + if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/source/length", + "ii", message_id, message_length); + VERBOSE3("OscSender: Sent [/update/source/length, ii, " << message_id << + ", " << message_length << "] to server '" << _server.hostname() << ":" + << _server.port() << "'."); + } + return true; +} + +/** + * Subscriber function called, when Publisher set the reference position. + * On server: Sends out OSC message to all clients to update to the given + * reference position. + * On client: Sends out OSC message to server about the updating of the + * reference position. + * @param position a Position representing the new reference position + */ +void ssr::OscSender::set_reference_position(const Position& position) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/reference/position", + "ff", position.x, position.y); + VERBOSE3("OscSender: Sent [/reference/position, ff, " << position.x << + ", " << position.y << "] to client '" << client->address().hostname() + << ":" << client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/reference/position", "ff", position.x, position.y); + VERBOSE3("OscSender: Sent [/update/reference/position, ff, " << position.x + << ", " << position.y << "] to server '" << _server.hostname() << ":" + << _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set the reference orientation. + * On server: Sends out OSC message to all clients to update to the given + * reference orientation. + * On client: Sends out OSC message to server about the updating of the + * reference orientation. + * @param orientation an Orientation representing the new reference orientation + */ +void ssr::OscSender::set_reference_orientation(const Orientation& orientation) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/reference/orientation", "f", orientation.azimuth); + VERBOSE3("OscSender: Sent [/reference/orientation, f, " << + orientation.azimuth << "] to client '" << + client->address().hostname() << ":" << client->address().port() << + "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/reference/orientation", "f", orientation.azimuth); + VERBOSE3("OscSender: Sent [/update/reference/orientation, f, " << + orientation.azimuth << "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set the reference offset + * position. + * On server: Sends out OSC message to all clients to update to the given + * reference offset position. + * On client: Sends out OSC message to server about the updating of the + * reference offset position. + * @param position a Position representing the new reference offset position + */ +void ssr::OscSender::set_reference_offset_position(const Position& position) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/reference_offset/position", "ff", position.x, position.y); + VERBOSE3("OscSender: Sent [/reference_offset/position, ff, " << + position.x << ", " << position.y << "] to client '" << + client->address().hostname() << ":" << client->address().port() << + "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/reference_offset/position", "ff", position.x, position.y); + VERBOSE3("OscSender: Sent [/update/reference_offset/position, ff, " << + position.x << ", " << position.y << "] to server '" << + _server.hostname() << ":" << _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set the reference offset + * orientation. + * On server: Sends out OSC message to all clients to update to the given + * reference offset orientation. + * On client: Sends out OSC message to server about the updating of the + * reference offset orientation. + * @param orientation an Orientation representing the new reference offset + * orientation + */ +void ssr::OscSender::set_reference_offset_orientation(const Orientation& + orientation) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/reference_offset/orientation", "f", orientation.azimuth); + VERBOSE3("OscSender: Sent [/reference_offset/orientation, f, " << + orientation.azimuth << "] to client '" << + client->address().hostname() << ":" << client->address().port() << + "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/reference_offset/orientation", "f", orientation.azimuth); + VERBOSE3("OscSender: Sent [/update/reference_offset/orientation, f, " << + orientation.azimuth << "] to server '" << _server.hostname() << ":" << + _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set the master volume. + * On server: Sends out OSC message to all clients to update to the given + * master volume (submits in dB!). + * On client: Sends out OSC message to server about the updating of the master + * volume (submits in dB!). + * @param volume float representing the new volume (linear scale) + */ +void ssr::OscSender::set_master_volume(float volume) +{ + float message_volume = apf::math::linear2dB(volume); + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/scene/volume", "f", + message_volume); + VERBOSE3("OscSender: Sent [/scene/volume, f, " << message_volume << + "] to client '" << client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/scene/volume", "f", + message_volume); + VERBOSE3("OscSender: Sent [/update/scene/volume, f, " << message_volume << + "] to server '" << _server.hostname() << ":" << _server.port() << + "'."); + } +} + +/** + * Subscriber function called, when Publisher set a source's output levels. + * On server: + * On client: + * @param id id_t representing the source + * @param first float* + * @param last float* + * @todo understand what should actually be done here + */ +void ssr::OscSender::set_source_output_levels(id_t id, float* first , float* + last) +{ + (void) id; + (void) first; + (void) last; +} + +/** + * Subscriber function called, when Publisher set the processing state. + * On server: Sends out OSC message to all clients to update their processing + * state. + * On client: Sends out OSC message to server about the update of its + * processing state. + * @param state bool representing the processing state + */ +void ssr::OscSender::set_processing_state(bool state) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), "/processing/state", + _handler.bool_to_message_type(state)); + VERBOSE3("OscSender: Sent [/processing/state, " << + _handler.bool_to_message_type(state) << "] to client '" << + client->address().hostname() << ":" << client->address().port() << + "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/processing/state", + _handler.bool_to_message_type(state)); + VERBOSE3("OscSender: Sent [/update/processing/state, " << + _handler.bool_to_message_type(state) << "] to server '" << + _server.hostname() << ":" << _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher sets the transport state. + * On server: Sends out OSC message to all clients to update their processing + * state. + * On client: Sends out OSC message to server about the update of its + * processing state. + * @param state a std::pair of a bool representing the processing state and + * jack_nframes_t, representing the current location in the transport + */ +void ssr::OscSender::set_transport_state( const std::pair& state) +{ + int32_t message_nframes = static_cast(state.second); + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active() && client->message_level() <= + MessageLevel::GUI_CLIENT) + { + client->address().send_from(_handler.server(), "/transport/state", + _handler.bool_to_message_type(state.first)); + VERBOSE3("OscSender: Sent [/transport/state, " << + _handler.bool_to_message_type(state.first) << "] to client '" << + client->address().hostname() << ":" << client->address().port() << + "'."); + client->address().send_from(_handler.server(), "/transport/seek", "i", + message_nframes); + VERBOSE3("OscSender: Sent [/transport/seek, i, " << message_nframes << + "] to client '" << client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default() && + _server.message_level() == MessageLevel::GUI_SERVER) + { + _server.address().send_from(_handler.server(), "/update/transport/state", + _handler.bool_to_message_type(state.first)); + VERBOSE3("OscSender: Sent [/update/transport/state, " << + _handler.bool_to_message_type(state.first) << "] to server '" << + _server.hostname() << ":" << _server.port() << "'."); + _server.address().send_from(_handler.server(), "/update/transport/seek", + "i", message_nframes); + VERBOSE3("OscSender: Sent [/update/transport/state, i, " << message_nframes + << "] to server '" << _server.hostname() << ":" << _server.port() << + "'."); + } +} + +/** + * Subscriber function called, when Publisher set whether to auto rotate + * sources. + * On server: Sends out OSC message to all clients to update their + * auto rotate settings. + * On client: Sends out OSC message to server about the update of its + * auto rotate settings. + * @param auto_rotate_sources a bool representing the current + * auto_rotate_sources setting. + */ +void ssr::OscSender::set_auto_rotation(bool auto_rotate_sources) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active()) + { + client->address().send_from(_handler.server(), + "/scene/auto_rotate_sources", + _handler.bool_to_message_type(auto_rotate_sources)); + VERBOSE3("OscSender: Sent [/scene/auto_rotate_sources, " << + _handler.bool_to_message_type(auto_rotate_sources) << + "] to client '" << client->address().hostname() << ":" << + client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), + "/update/scene/auto_rotate_sources", + _handler.bool_to_message_type(auto_rotate_sources)); + VERBOSE3("OscSender: Sent [/update/scene/auto_rotate_sources, " << + _handler.bool_to_message_type(auto_rotate_sources) << "] to server '" + << _server.hostname() << ":" << _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set cpu_load. + * On server: Does nothing. + * On client: Sends out OSC message to server indicating the cpu_load. + * @param load a float representing the current cpu load + * @todo implement pooling of cpu_load updates + */ +void ssr::OscSender::set_cpu_load(float load) +{ + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active() && client->message_level() == + MessageLevel::GUI_CLIENT) + { + client->address().send_from(_handler.server(), "/cpu_load", "f", load); + VERBOSE3("OscSender: Sent [/cpu_load, f, " << apf::str::A2S(load) << + "] to client '" << client->hostname() << ":" << client->port() << + "'."); + } + } + } + else if(_handler.is_client() && !server_is_default() && + _server.message_level() == MessageLevel::GUI_SERVER) + { + _server.address().send_from(_handler.server(), "/update/cpu_load", "f", + load); + VERBOSE3("OscSender: Sent [/update/cpu_load, f, " << apf::str::A2S(load) << + "] to server '" << _server.hostname() << ":" << _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set sample rate. + * On server: Does nothing, as the sample rate is set during SSR startup and + * can not be changed during runtime. + * On client: Sends out OSC message to server about the update of its sample + * rate. + * @param sr an integer representing the current sample rate. + */ +void ssr::OscSender::set_sample_rate(int sr) +{ + int32_t message_sr = static_cast(sr); + if(_handler.is_client() && !server_is_default()) + { + _server.address().send_from(_handler.server(), "/update/scene/sample_rate", + "i", message_sr); + VERBOSE3("OscSender: Sent [/update/scene/sample_rate, i, " << + apf::str::A2S(message_sr) << "] to server '" << _server.hostname() << + ":" << _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set the master signal level. + * On server: Sends out OSC message to clients with MessageLevel GUI_CLIENT + * On client: Sends out OSC message to server with MessageLevel GUI_SERVER + * about the update of its signal level. + * @param level a float representing the current master signal level. + */ +void ssr::OscSender::set_master_signal_level(float level) +{ + float message_level(apf::math::linear2dB(level)); + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active() && client->message_level() == + MessageLevel::GUI_CLIENT) + { + client->address().send_from(_handler.server(), + "/scene/master_signal_level", "f", message_level); + VERBOSE3("OscSender: Sent [/scene/master_signal_level, f, " << + apf::str::A2S(message_level) << "] to client '" << + client->address().hostname() << ":" << client->address().port() << + "'."); + } + } + } + else if(_handler.is_client() && !server_is_default() && + _server.message_level() == MessageLevel::GUI_SERVER) + { + _server.address().send_from(_handler.server(), + "/update/scene/master_signal_level", "f", message_level); + VERBOSE3("OscSender: Sent [/update/scene/master_signal_level, f, " << + apf::str::A2S(message_level) << "] to server '" << _server.hostname() << + ":" << _server.port() << "'."); + } +} + +/** + * Subscriber function called, when Publisher set a source's signal level. + * On server: Sends out OSC message to all clients to update a source's signal + * level. + * On client: Sends out OSC message to server about the update of the source's + * signal level. + * @param id an id_t representing the source. + * @param level a float representing the signal level of the source. + * @return true + */ +bool ssr::OscSender::set_source_signal_level(const id_t id, const float& level) +{ + int32_t message_id = static_cast(id); + float message_level(apf::math::linear2dB(level)); + if(_handler.is_server()) + { + for (const auto& client: _clients) + { + if(client && client->active() && client->message_level() == + MessageLevel::GUI_CLIENT) + { + client->address().send_from(_handler.server(), "/source/level", "if", + message_id, message_level); + VERBOSE3("OscSender: Sent [/source/level, if, " << message_id << ", " + << message_level << "] to client '" << client->address().hostname() + << ":" << client->address().port() << "'."); + } + } + } + else if(_handler.is_client() && !server_is_default() && + _server.message_level() == MessageLevel::GUI_SERVER) + { + _server.address().send_from(_handler.server(), "/update/source/level", + "if", message_id, message_level); + VERBOSE3("OscSender: Sent [/update/source/level, if, " << + apf::str::A2S(message_id) << ", " << apf::str::A2S(message_level) << + "] to server '" << _server.hostname() << ":" << _server.port() << "'."); + } + return true; +} + diff --git a/src/networking/oscsender.h b/src/networking/oscsender.h new file mode 100644 index 00000000..338a9a7c --- /dev/null +++ b/src/networking/oscsender.h @@ -0,0 +1,127 @@ +/** + * Header for OscSender, declaring a class, responsible for sending OSC + * messages and subscribing to the SSR's Publisher. + * @file oscsender.h + */ + +#ifndef OSC_SENDER_H +#define OSC_SENDER_H + +#ifdef HAVE_CONFIG_H +#include // for ENABLE_* +#endif + +#include +#include +#include +#include +#include "oscclient.h" +#include "ssr_global.h" // for VERBOSE, MessageLevel +#include "subscriber.h" +#include "apf/parameter_map.h" + +namespace ssr +{ + +/** + * OscSender + * This class holds a Publisher and an OscHandler reference, while implementing + * the Subscriber interface. + * The Publisher is subscribed to, using its interface to send out OSC messages + * on all events it emmits. + * @todo implement sending levels periodically using apf/cxx_thread_policy.h + * @todo implement polling clients periodically using apf/cxx_thread_policy.h + */ +class OscSender : public Subscriber +{ + private: + // reference to controller + Publisher& _controller; + // reference to handler + OscHandler& _handler; + // server object (client) + OscClient _server; + // bool, indicating if subscribed to _controller + bool _is_subscribed; + // vector of pointers to OscClient objects (server) + std::vector _clients; + // map of id/parameter_map pairs for new sources (server) + std::map _new_sources; + // thread used for calling poll_all_clients continuously + std::thread _poll_thread; + const unsigned int _poll_milliseconds{1000}; + bool _poll_all_clients; + typedef std::map source_level_map_t; + source_level_map_t _source_levels; + float _master_level; + + void poll_all_clients(); + void remove_all_clients(); + bool is_new_source(id_t id); //< check, if source id is in _new_sources + bool is_complete_source(id_t id); //< check, if source is complete + void send_new_source_message_from_id(id_t id); //< creates a 'new source' OSC message + + public: + OscSender(Publisher& controller, OscHandler& handler); + ~OscSender(); + + void start(); + void stop(); + void set_server_address(std::string& hostname, std::string& port); + lo::Address& server_address(); + void set_server_message_level(MessageLevel message_level); + bool server_is_default(); + bool is_server(std::string& hostname, std::string& port); + void set_message_level(const unsigned int& message_level); + void add_client(std::string hostname, std::string port, ssr::MessageLevel + message_level); + void set_client_message_level(std::string& hostname, std::string& port, + ssr::MessageLevel message_level); + bool client_has_message_level(std::string& hostname, std::string& port, + ssr::MessageLevel message_level); + void deactivate_client(std::string hostname, std::string port); + void increment_client_alive_counter(std::string& hostname, std::string& + port); + void send_levels(); + + // Subscriber Interface + virtual void set_loudspeakers(const Loudspeaker::container_t& + loudspeakers); + virtual void new_source(id_t id); + virtual void delete_source(id_t id); + virtual void delete_all_sources(); + virtual bool set_source_position(id_t id, const Position& position); + virtual bool set_source_position_fixed(id_t id, const bool& fix); + virtual bool set_source_orientation(id_t id, const Orientation& + orientation); + virtual bool set_source_gain(id_t id, const float& gain); + virtual bool set_source_mute(id_t id, const bool& mute); + virtual bool set_source_name(id_t id, const std::string& name); + virtual bool set_source_properties_file(id_t id, const std::string& name); + virtual bool set_source_model(id_t id, const Source::model_t& model); + virtual bool set_source_port_name(id_t id, const std::string& port_name); + virtual bool set_source_file_name(id_t id, const std::string& file_name); + virtual bool set_source_file_channel(id_t id, const int& file_channel); + virtual bool set_source_file_length(id_t id, const long int& length); + virtual void set_reference_position(const Position& position); + virtual void set_reference_orientation(const Orientation& orientation); + virtual void set_reference_offset_position(const Position& position); + virtual void set_reference_offset_orientation(const Orientation& + orientation); + virtual void set_master_volume(float volume); + virtual void set_source_output_levels(id_t id, float* first, float* last); + virtual void set_processing_state(bool state); + virtual void set_transport_state( const std::pair& + state); + virtual void set_auto_rotation(bool auto_rotate_sources); + virtual void set_decay_exponent(float exponent); + virtual void set_amplitude_reference_distance(float distance); + virtual void set_master_signal_level(float level); + virtual void set_cpu_load(float load); + virtual void set_sample_rate(int sample_rate); + virtual bool set_source_signal_level(const id_t id, const float& level); + +}; + +} // namespace ssr +#endif diff --git a/src/publisher.h b/src/publisher.h index f8f7ea87..be4c67b0 100644 --- a/src/publisher.h +++ b/src/publisher.h @@ -113,6 +113,8 @@ struct Publisher set_source_port_name(id_t id, const std::string& port_name) = 0; virtual void set_source_position_fixed(id_t id, const bool fix) = 0; + virtual void set_source_file_channel(id_t id, const int& channel) = 0; + virtual void set_source_file_name(id_t id, const std::string& file_name) = 0; /// set position of the reference /// @param position well, the position diff --git a/src/ssr_global.h b/src/ssr_global.h index 9e269eb2..e8dbdc63 100644 --- a/src/ssr_global.h +++ b/src/ssr_global.h @@ -51,6 +51,14 @@ extern float c; ///< speed of sound (meters per second) extern float c_inverse; ///< reciprocal value of c /// time to sleep after connecting a new soundfile with ecasound extern unsigned int usleeptime; +enum class MessageLevel : id_t +{ + CLIENT = 0, + GUI_CLIENT, + SERVER, + GUI_SERVER, + MAX_VALUE = GUI_SERVER +}; } // namespace ssr diff --git a/supercollider/tests.scd b/supercollider/tests.scd new file mode 100644 index 00000000..61c8099d --- /dev/null +++ b/supercollider/tests.scd @@ -0,0 +1,227 @@ +// tests + +// evaluate block to add the test functions +( + +~processingTestRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 20.do({address.sendMsg("/processing/state", [$F, $T].choose)}); +}; + +~referenceTestRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 100.do({address.sendMsg("/reference/orientation", 360.0.rand2)}); + 20.do({address.sendMsg("/reference/position", 10.0.rand2, 10.0.rand2)}); + 100.do({address.sendMsg("/reference_offset/orientation", 360.0.rand2)}); + 20.do({address.sendMsg("/reference_offset/position", 10.0.rand2, 10.0.rand2)}); +}; + +~sceneTestRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 20.do({address.sendMsg("/scene/amplitude_reference_distance", 10.0.rand2)}); + 10.do({address.sendMsg("/scene/auto_rotate_sources", [$F, $T].choose)}); + address.sendMsg("/scene/clear"); + 20.do({address.sendMsg("/scene/decay_exponent", rrand(-1, 10))}); + address.sendMsg("/scene/load", "some_file.asdf"); + address.sendMsg("/scene/save", "some_file.asdf"); + 100.do({address.sendMsg("/scene/master_signal_level", rrand(-100.0, 40.0))}); + 100.do({address.sendMsg("/scene/volume", rrand(-100.0, 40.0))}); +}; + +~sourceTestRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 20.do({address.sendMsg("/source/delete", 10.rand2)}); + 20.do({address.sendMsg("/source/file_channel", 10.rand2, 10.rand)}); + 20.do({address.sendMsg("/source/file_name_or_port_number", 10.rand2, 10.rand2.asString)}); + 20.do({address.sendMsg("/source/gain", 10.rand2, rrand(-100.0, 40.0))}); + 100.do({address.sendMsg("/source/level", 10.rand2, rrand(-100.0, 40.0))}); + 20.do({address.sendMsg("/source/model", 10.rand2, ["unknown", "point", "plane", "line", "directional", "extended"].choose)}); + 20.do({address.sendMsg("/source/mute", 10.rand2, [$F, $T].choose)}); + 20.do({address.sendMsg("/source/name", 10.rand2, ("source_"++10.rand2.asString))}); + 20.do({address.sendMsg("/source/new", ("source_"++10.rand2.asString), ["unknown", + "point", "plane", "line", "directional", "extended"].choose, + 10.rand2.asString, 10.0.rand2, 10.0.rand2, rrand(-100.0, 40.0), rrand(-100.0, 40.0), + [$F, $T].choose, [$F, $T].choose, [$F, $T].choose) + }); + 20.do({address.sendMsg("/source/new", ("source_"++10.rand2.asString), ["unknown", + "point", "plane", "line", "directional", "extended"].choose, + 10.rand2.asString, 10.0.rand2, 10.0.rand2, rrand(-100.0, 40.0), rrand(-100.0, 40.0), + 10.rand2, ("file_"++10.rand2.asString), [$F, $T].choose, [$F, $T].choose, + [$F, $T].choose) + }); + 20.do({address.sendMsg("/source/orientation", 10.rand2, 360.0.rand2)}); + 20.do({address.sendMsg("/source/port_name", 10.rand2, 10.rand2.asString)}); + 20.do({address.sendMsg("/source/position", 10.rand2, 10.0.rand2, 10.0.rand2)}); + 20.do({address.sendMsg("/source/position_fixed", 10.rand2, [$F, $T].choose)}); + 20.do({address.sendMsg("/source/properties_file", 10.rand2, 10.rand2.asString)}); +}; + +~trackerTestRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 10.do({address.sendMsg("/tracker/reset")}); +}; + +~transportTestRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 100.do({address.sendMsg("/transport/rewind")}); + 100.do({address.sendMsg("/transport/seek", (10.rand2.asString++":"++59.rand2.asString++":"++59.0.rand2.asString))}); + 100.do({address.sendMsg("/transport/state", [$F, $T].choose)}); +}; + +~updateTestRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 100.do({address.sendMsg("/update/cpu_load", 100.0.rand)}); + 100.do({address.sendMsg("/update/processing/state", [$F, $T].choose)}); + 100.do({address.sendMsg("/update/reference/orientation", 360.0.rand2)}); + 20.do({address.sendMsg("/update/reference/position", 10.0.rand2, 10.0.rand2)}); + 20.do({address.sendMsg("/update/reference_offset/orientation", 360.0.rand2)}); + 20.do({address.sendMsg("/update/reference_offset/position", 10.0.rand2, 10.0.rand2)}); + 20.do({address.sendMsg("/update/scene/amplitude_reference_distance", 10.0.rand2)}); + 20.do({address.sendMsg("/update/scene/auto_rotate_sources", [$F, $T].choose)}); + 20.do({address.sendMsg("/update/scene/decay_exponent", rrand(-1.0, 10.0))}); + 20.do({address.sendMsg("/update/scene/master_signal_level", rrand(-100.0, 40.0))}); + 20.do({address.sendMsg("/update/scene/sample_rate", [22050, 44100, 48000, 88200, 96000].choose)}); + 20.do({address.sendMsg("/update/scene/volume", rrand(-100.0, 40.0))}); + 20.do({address.sendMsg("/update/source/delete", 10.rand2)}); + 20.do({address.sendMsg("/update/source/file_channel", 10.rand2, 10.rand2)}); + 20.do({address.sendMsg("/update/source/file_name_or_port_number", 10.rand2, 10.rand2.asString)}); + 20.do({address.sendMsg("/update/source/gain", 10.rand2, rrand(-100.0, 40.0))}); + 20.do({address.sendMsg("/update/source/level", 10.rand2, rrand(-100.0, 40.0))}); + 20.do({address.sendMsg("/update/source/model", 10.rand2, ["unknown", "point", "plane", "line", "directional", "extended"].choose)}); + 20.do({address.sendMsg("/update/source/mute", 10.rand2, [$F, $T].choose)}); + 20.do({address.sendMsg("/update/source/name", 10.rand2, ("source_"++10.rand2.asString))}); + 20.do({address.sendMsg("/update/source/new", 10.rand2)}); + 20.do({address.sendMsg("/update/source/orientation", 10.rand2, 360.0.rand2)}); + 20.do({address.sendMsg("/update/source/port_name", 10.rand2, ("in_"++10.rand2))}); + 20.do({address.sendMsg("/update/source/position", 10.rand2, 10.0.rand2, 10.0.rand2)}); + 20.do({address.sendMsg("/update/source/position_fixed", 10.rand2, [$F, $T].choose)}); + 20.do({address.sendMsg("/update/source/properties_file", 10.rand2, ("file_"++10.rand2.asString))}); + 20.do({address.sendMsg("/update/transport/seek", (10.rand2.asString++":"++59.rand2.asString++":"++59.0.rand2.asString))}); + 100.do({address.sendMsg("/update/transport/state", [$F, $T].choose)}); +}; + +~messageLevelTestServerRandomAll = { arg address = NetAddr("127.0.0.1", 50001), otherClient = NetAddr("127.0.0.1", 50002); + 20.do({address.sendMsg("/message_level", 10.rand2)}); + 20.do({address.sendMsg("/message_level", otherClient.ip.asString, otherClient.port.asString, 10.rand2)}); +}; + +~subscribeTestOtherClient = { arg address = NetAddr("127.0.0.1", 50001), otherClient = NetAddr("127.0.0.1", 50002); + 10.do({ + address.sendMsg("/subscribe", [$F, $T].choose, otherClient.ip, otherClient.port); + address.sendMsg("/subscribe", $T, otherClient.ip, otherClient.port, 10.rand2); + address.sendMsg("/subscribe", [$F, $T].choose); + address.sendMsg("/subscribe", $T, 10.rand2); + address.sendMsg("/subscribe", $F); + }); +}; + +~cpuLoadTestClient = { arg address = NetAddr("127.0.0.1", 50001); + 100.do({address.sendMsg("/cpu_load", 100.0.rand)}); +}; + +~messageLevelTestClientRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 20.do({address.sendMsg("/message_level", 10.rand2)}); +}; + +~pollTestClientRandomAll = { arg address = NetAddr("127.0.0.1", 50001); + 100.do({address.sendMsg("/poll")}); +}; + +~sourceTestAdding = { arg address = NetAddr("127.0.0.1", 50001), amountOfSources = 2; + amountOfSources.do({|item,i| + address.sendMsg("/source/new", ("source_"++(i+1).asString), "point", + (i+1).asString, 10.0.rand2, 10.0.rand2, -6.0, -6.0, $F, $T, $F); + }); +}; + +~sourceTestMoving = { arg address = NetAddr("127.0.0.1", 50001), amountOfSources = 2; + Routine{ + 1.wait; + 1000.do({ + amountOfSources.do({|item, i| + address.sendMsg("/source/position", i+1, 10.0.rand2, 10.0.rand2); + }); + 0.1.wait; + }); + }.play; +}; + +) + + +// sclang tries to control/send to server (not subscribed) +( + // print all OSC messages sent to sclang + OSCFunc.trace(true, true); + ~messageLevelTestServerRandomAll.value; + ~sourceTestRandomAll.value; + ~updateTestRandomAll.value; + ~subscribeTestOtherClient.value; + ~processingTestRandomAll.value; + ~transportTestRandomAll.value; + ~trackerTestRandomAll.value; + ~referenceTestRandomAll.value; + ~sceneTestRandomAll.value; +) + +// sclang controls server (subscribed) +( + // print all OSC messages sent to sclang + OSCFunc.trace(true, true); + // set address of the server instance + ~address = NetAddr("127.0.0.1", 50001); + // subscribe to server with MessageLevel::SERVER + ~address.sendMsg("/subscribe", $T, 2); + // send alive message on subsequent poll + ~responder_poll = OSCFunc( + { |msg, time, addr, recvPort| + ~address.sendMsg("/alive"); + }, '/poll' + , ~address + ); + ~messageLevelTestServerRandomAll.value; + ~sourceTestRandomAll.value; + ~updateTestRandomAll.value; + ~processingTestRandomAll.value; + ~transportTestRandomAll.value; + ~trackerTestRandomAll.value; + ~referenceTestRandomAll.value; + ~sceneTestRandomAll.value; +) + +// sclang tries to control client (not polled) +( + // print all OSC messages sent to sclang + OSCFunc.trace(true, true); + ~messageLevelTestClientRandomAll.value; + ~cpuLoadTestClient.value; + ~sourceTestRandomAll.value; + ~processingTestRandomAll.value; + ~transportTestRandomAll.value; + ~trackerTestRandomAll.value; + ~referenceTestRandomAll.value; + ~sceneTestRandomAll.value; +) + +// sclang tries to control client (polled) +( + // print all OSC messages sent to sclang + OSCFunc.trace(true, true); + // set address of the server instance + ~address = NetAddr("127.0.0.1", 50001); + ~pollTestClientRandomAll.value; + ~messageLevelTestClientRandomAll.value; + ~cpuLoadTestClient.value; + ~sourceTestRandomAll.value; + ~processingTestRandomAll.value; + ~transportTestRandomAll.value; + ~trackerTestRandomAll.value; + ~referenceTestRandomAll.value; + ~sceneTestRandomAll.value; +) + +// sclang controls a client (polled), adds sources and moves them +( + // print all OSC messages sent to sclang + OSCFunc.trace(true, true); + // set address of the server instance + ~address = NetAddr("127.0.0.1", 50001); + // poll client instance to make it subscribe + ~address.sendMsg("/poll"); + ~sourceTestAdding.value(amountOfSources: 20); + ~sourceTestMoving.value(amountOfSources: 20); +) + diff --git a/supercollider/workflows.scd b/supercollider/workflows.scd new file mode 100644 index 00000000..cf7a92e3 --- /dev/null +++ b/supercollider/workflows.scd @@ -0,0 +1,164 @@ +// workflows + +// sclang is a client, controlling a SSR server instance +( + // set address of the client instance + ~address = NetAddr("localhost", 50001); + // print all OSC messages sent to sclang + OSCFunc.trace(true, true); + // subscribe to server with MessageLevel::SERVER + ~address.sendMsg("/subscribe", $T, 2); + // add new source with standard input at -1.0/1.0 + ~address.sendMsg("/source/new", "in_1", "point", "1", + -1.0, 1.0, 0.1, 0.1, 0, "1", $F, $F, $T); + // add new source with standard input at 1.0/1.0 + ~address.sendMsg("/source/new", "in_2", "point", "2", + 1.0, 1.0, 0.1, 0.1, 0, "1", $F, $F, $T); + // unmute source 1 + ~address.sendMsg("/source/mute", 1, $F); + // unmute source 2 + ~address.sendMsg("/source/mute", 2, $F); + // move source 1 to -2.0/2.0 + ~address.sendMsg("/source/position", 1, -2.0, 2.0); + // move source 2 to 2.0/2.0 + ~address.sendMsg("/source/position", 2, 2.0, 2.0); + // remove all sources + ~address.sendMsg("/scene/clear"); + // unsubscribe from server + ~address.sendMsg("/subscribe", $F); +) + +// sclang is a server, controlling a SSR client instance +( + // set address of the client instance + ~address = NetAddr("localhost", 50001); + // print all OSC messages sent to sclang + OSCFunc.trace(true, true); + // poll client instance to make it subscribe + ~address.sendMsg("/poll"); + // subsequent poll makes client emit /alive message + ~address.sendMsg("/poll"); + // add new source with standard input at -1.0/1.0 + ~address.sendMsg("/source/new", "in_1", "point", "1", + -1.0, 1.0, 0.1, 0.1, 0, "1", $F, $F, $T); + // add new source with standard input at 1.0/1.0 + ~address.sendMsg("/source/new", "in_2", "point", "2", + 1.0, 1.0, 0.1, 0.1, 0, "1", $F, $F, $T); + // unmute source 1 + ~address.sendMsg("/source/mute", 1, $F); + // unmute source 2 + ~address.sendMsg("/source/mute", 2, $F); + // set message level to GUI_SERVER (a lot of messages!) + ~address.sendMsg("/message_level", 3); + // move source 1 to -2.0/2.0 + ~address.sendMsg("/source/position", 1, -2.0, 2.0); + // move source 2 to 2.0/2.0 + ~address.sendMsg("/source/position", 2, 2.0, 2.0); + // set message level back to SERVER + ~address.sendMsg("/message_level", 1); + // remove all sources + ~address.sendMsg("/scene/clear"); +) + +// sclang is a client, controlling a SSR server instance, while forwarding OSC +// commands from a smartphone, that uses Sensors2OSC (https://sensors2.org/osc/) +( + // set address of the client instance + ~address = NetAddr("localhost", 50001); + // print all OSC messages sent to sclang + // subscribe to server with MessageLevel::SERVER + ~address.sendMsg("/subscribe", $T, 2); + // add new source with standard input at -1.0/1.0 + ~address.sendMsg("/source/new", "in_1", "point", "1", -1.0, 1.0, 0.0, 0.99, + $F, $F, $F); + // add new source with standard input at 1.0/1.0 + ~address.sendMsg("/source/new", "in_2", "point", "2", 1.0, 1.0, 0.0, 0.99, + $F, $F, $F); + // vectors for holding source position + ~source1 = [-1.0, -1.0]; + ~source2 = [-1.0, -1.0]; + // OSC functions to receive from Sensors2OSC and send to SSR + OSCFunc( + { + arg msg, time, addr, recvPort; + ~address.sendMsg("/alive"); + }, + '/poll' + ); + OSCFunc( + { + arg msg, time, addr, recvPort; + ~address.sendMsg("/reference/orientation", msg[1]+90); + }, + '/orientation/X' + ); + OSCFunc( + { + arg msg, time, addr, recvPort; + if(msg[1]>=0.0, { + ~source1[0] = msg[1]; + }); + if(msg[0] != (-1.0) && msg[1] != (-1.0), { + ~address.sendMsg("/source/position", 1, + ~source1[0].linlin(0.0, 1.0, -10.0, 10.0), + ~source1[1].linlin(0.0, 1.0, -10.0, 10.0) + ); + }); + }, + '/touch1/X' + ); + OSCFunc( + { + arg msg, time, addr, recvPort; + if(msg[1]>=0.0, { + ~source1[1] = msg[1]; + }); + if(msg[0] != (-1.0) && msg[1] != (-1.0), { + ~address.sendMsg("/source/position", 1, + ~source1[0].linlin(0.0, 1.0, -10.0, 10.0), + ~source1[1].linlin(0.0, 1.0, -10.0, 10.0) + ); + }); + }, + '/touch1/Y' + ); + OSCFunc( + { + arg msg, time, addr, recvPort; + if(msg[1]>=0.0, { + ~source2[0] = msg[1]; + }); + if(msg[0] != (-1.0) && msg[1] != (-1.0), { + ~address.sendMsg("/source/position", 2, + ~source2[0].linlin(0.0, 1.0, -10.0, 10.0), + ~source2[1].linlin(0.0, 1.0, -10.0, 10.0) + ); + }); + }, + '/touch2/X' + ); + OSCFunc( + { + arg msg, time, addr, recvPort; + if(msg[1]>=0.0, { + ~source2[1] = msg[1]; + }); + if(msg[0] != (-1.0) && msg[1] != (-1.0), { + ~address.sendMsg("/source/position", 2, + ~source2[0].linlin(0.0, 1.0, -10.0, 10.0), + ~source2[1].linlin(0.0, 1.0, -10.0, 10.0) + ); + }); + }, + '/touch2/Y' + ); +) + +// clear scene and unsubscribe from server instance +( + // remove all sources + ~address.sendMsg("/scene/clear"); + // unsubscribe from server + ~address.sendMsg("/subscribe", $F); +) +