andrewm@0: /* andrewm@0: TouchKeys: multi-touch musical keyboard control software andrewm@0: Copyright (c) 2013 Andrew McPherson andrewm@0: andrewm@0: This program is free software: you can redistribute it and/or modify andrewm@0: it under the terms of the GNU General Public License as published by andrewm@0: the Free Software Foundation, either version 3 of the License, or andrewm@0: (at your option) any later version. andrewm@0: andrewm@0: This program is distributed in the hope that it will be useful, andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrewm@0: GNU General Public License for more details. andrewm@0: andrewm@0: You should have received a copy of the GNU General Public License andrewm@0: along with this program. If not, see . andrewm@0: andrewm@0: ===================================================================== andrewm@0: andrewm@0: Osc.h: classes for handling reception and transmission of OSC messages, andrewm@0: using the liblo library. andrewm@0: */ andrewm@0: andrewm@0: #ifndef OSC_H andrewm@0: #define OSC_H andrewm@0: andrewm@22: //#include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@20: #include "lo/lo.h" andrewm@0: #include "../JuceLibraryCode/JuceHeader.h" andrewm@0: andrewm@0: using namespace std; andrewm@0: andrewm@0: class OscMessageSource; andrewm@0: andrewm@0: // This is an abstract base class implementing a single function oscHandlerMethod(). Objects that andrewm@0: // want to register to receive OSC messages should inherit from OscHandler. Notice that all listener andrewm@0: // add/remove methods are private or protected. The subclass of OscHandler should add any relevant andrewm@0: // listeners, or optionally expose a public interface to add listeners. (Never call the methods in andrewm@0: // OscMessageSource externally.) andrewm@0: andrewm@0: class OscHandler andrewm@0: { andrewm@0: public: andrewm@0: OscHandler() : oscController_(NULL) {} andrewm@0: andrewm@0: // The OSC controller will call this method when it gets a matching message that's been registered andrewm@0: virtual bool oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) = 0; andrewm@0: void setOscController(OscMessageSource *c) { oscController_ = c; } andrewm@0: andrewm@0: virtual ~OscHandler(); // In the destructor, remove all OSC listeners andrewm@0: protected: andrewm@0: bool addOscListener(const string& path); andrewm@0: bool removeOscListener(const string& path); andrewm@0: bool removeAllOscListeners(); andrewm@0: andrewm@0: OscMessageSource *oscController_; andrewm@0: set oscListenerPaths_; andrewm@0: }; andrewm@0: andrewm@0: // Base class for anything that acts as a source of OSC messages. Could be andrewm@0: // received externally or internally generated. andrewm@0: andrewm@0: class OscMessageSource andrewm@0: { andrewm@0: friend class OscHandler; andrewm@0: andrewm@0: public: andrewm@0: OscMessageSource() {} andrewm@0: andrewm@0: protected: andrewm@9: bool addListener(const string& path, OscHandler *object, andrewm@9: bool matchSubpath = false); // Add a listener object for a specific path andrewm@0: bool removeListener(const string& path, OscHandler *object); // Remove a listener object from a specific path andrewm@0: bool removeListener(OscHandler *object); // Remove a listener object from all paths andrewm@0: andrewm@0: void updateListeners(); // Propagate changes to the listeners to the main object andrewm@0: andrewm@0: //ReadWriteLock oscListenerMutex_; // This mutex protects the OSC listener table from being modified mid-message andrewm@0: CriticalSection oscListenerMutex_; // This mutex protects the OSC listener table from being modified mid-message andrewm@0: CriticalSection oscUpdaterMutex_; // This mutex controls the insertion of objects in add/removeListener andrewm@0: andrewm@0: multimap noteListeners_; // Map from OSC path name to handler (possibly multiple handlers per object) andrewm@0: multimap noteListenersToAdd_; // Collection of listeners to add on the next cycle andrewm@0: multimap noteListenersToRemove_; // Collection of listeners to remove on the next cycle andrewm@0: set noteListenersForBlanketRemoval_; // Collection of listeners to remove from all paths andrewm@0: }; andrewm@0: andrewm@0: // This class specifically implements OSC messages coming from external sources andrewm@0: andrewm@0: class OscReceiver : public OscMessageSource andrewm@0: { andrewm@0: public: andrewm@6: OscReceiver(const int port, const char *prefix) { andrewm@6: globalPrefix_.assign(prefix); andrewm@0: useThru_ = false; andrewm@6: andrewm@6: // Only start the server if the port is positive andrewm@6: if(port > 0) { andrewm@6: char portStr[16]; andrewm@20: #ifdef _MSC_VER andrewm@20: _snprintf_s(portStr, 16, _TRUNCATE, "%d", port); andrewm@20: #else andrewm@6: snprintf(portStr, 16, "%d", port); andrewm@20: #endif andrewm@20: andrewm@6: oscServerThread_ = lo_server_thread_new(portStr, staticErrorHandler); andrewm@6: if(oscServerThread_ != 0) { andrewm@6: lo_server_thread_add_method(oscServerThread_, NULL, NULL, OscReceiver::staticHandler, (void *)this); andrewm@6: lo_server_thread_start(oscServerThread_); andrewm@6: } andrewm@6: } andrewm@6: else andrewm@6: oscServerThread_ = 0; andrewm@6: } andrewm@0: andrewm@0: void setThruAddress(lo_address thruAddr, const char *prefix) { andrewm@0: thruAddress_ = thruAddr; andrewm@0: thruPrefix_.assign(prefix); andrewm@0: useThru_ = true; andrewm@0: } andrewm@6: andrewm@6: // Check whether the server is operating andrewm@6: bool running() { return (oscServerThread_ != 0); } andrewm@6: andrewm@6: // Get or set the current port. Setting the port requires restarting the server. andrewm@6: // setPort() returns true on success; false if an error occurred (which will leave the server not running). andrewm@6: const int port() { andrewm@6: if(oscServerThread_ == 0) andrewm@6: return 0; andrewm@6: return lo_server_get_port(oscServerThread_); andrewm@6: } andrewm@6: bool setPort(const int port); andrewm@0: andrewm@0: // staticHandler() is called by liblo with new OSC messages. Its only function is to pass control andrewm@0: // to the object-specific handler method, which has access to all internal variables. andrewm@0: andrewm@0: int handler(const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *data); andrewm@0: static int staticHandler(const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *userData) { andrewm@0: return ((OscReceiver *)userData)->handler(path, types, argv, argc, msg, userData); andrewm@6: } andrewm@6: andrewm@6: // staticErrorHandler() is called by liblo when an error occurs. For now, ignore errors. andrewm@6: andrewm@6: static void staticErrorHandler(int num, const char *msg, const char *path) {} andrewm@0: andrewm@0: ~OscReceiver() { andrewm@6: if(oscServerThread_ != 0) { andrewm@6: lo_server_thread_del_method(oscServerThread_, NULL, NULL); andrewm@6: lo_server_thread_stop(oscServerThread_); andrewm@6: lo_server_thread_free(oscServerThread_); andrewm@6: } andrewm@6: } andrewm@0: andrewm@0: private: andrewm@0: lo_server_thread oscServerThread_; // Thread that handles received OSC messages andrewm@0: andrewm@0: // OSC thru andrewm@0: bool useThru_; // Whether or not we retransmit any messages andrewm@0: lo_address thruAddress_; // Address to which we retransmit andrewm@0: string thruPrefix_; // Prefix that must be matched to be retransmitted andrewm@0: andrewm@0: // State variables andrewm@0: string globalPrefix_; // Prefix for all OSC paths andrewm@0: }; andrewm@0: andrewm@49: // Simple class to hold a message alongw ith a path and a type andrewm@49: class OscMessage andrewm@49: { andrewm@49: public: andrewm@49: OscMessage(const char *path, const char *type, lo_message& message) andrewm@49: : path_(path), type_(type), message_(message) {} andrewm@49: andrewm@49: ~OscMessage() { andrewm@49: lo_message_free(message_); andrewm@49: } andrewm@49: andrewm@49: const char *path() { return path_.c_str(); } andrewm@49: const char *type() { return type_.c_str(); } andrewm@49: lo_message message() { return message_; } andrewm@49: andrewm@49: // Add a prefix to the message path andrewm@49: void prependPath(const char *prefix) { andrewm@49: path_.insert(0, prefix); // TODO: check that this is right andrewm@49: } andrewm@49: andrewm@49: private: andrewm@49: std::string path_; andrewm@49: std::string type_; andrewm@49: lo_message message_; andrewm@49: }; andrewm@49: andrewm@49: andrewm@0: class OscTransmitter andrewm@0: { andrewm@0: public: andrewm@0: OscTransmitter() : enabled_(true), debugMessages_(false) {} andrewm@0: andrewm@0: // Enable or disable transmission andrewm@0: void setEnabled(bool enable) { enabled_ = enable; } andrewm@0: bool enabled() { return enabled_; } andrewm@0: andrewm@0: // Add and remove addresses to send to andrewm@0: int addAddress(const char * host, const char * port, int proto = LO_UDP); andrewm@0: void removeAddress(int index); andrewm@0: void clearAddresses(); andrewm@0: vector addresses() { return addresses_; } andrewm@0: andrewm@0: void sendMessage(const char * path, const char * type, ...); andrewm@0: void sendMessage(const char * path, const char * type, const lo_message& message); andrewm@0: void sendByteArray(const char * path, const unsigned char * data, int length); andrewm@0: andrewm@0: void setDebugMessages(bool debug) { debugMessages_ = debug; } andrewm@0: andrewm@0: ~OscTransmitter(); andrewm@49: andrewm@49: // Static methods andrewm@49: static OscMessage* createMessage(const char * path, const char * type, ...); andrewm@49: static OscMessage* createSuccessMessage() { return createMessage("/result", "i", 0, LO_ARGS_END); } andrewm@49: static OscMessage* createFailureMessage() { return createMessage("/result", "i", 1, LO_ARGS_END); } andrewm@0: andrewm@0: private: andrewm@0: vector addresses_; andrewm@0: bool enabled_; andrewm@0: bool debugMessages_; andrewm@0: }; andrewm@0: andrewm@0: #endif // OSC_H