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.cpp: classes for handling reception and transmission of OSC messages,
andrewm@0: using the liblo library.
andrewm@0: */
andrewm@0:
andrewm@0: #include "Osc.h"
andrewm@0:
andrewm@0: #undef DEBUG_OSC
andrewm@0:
andrewm@0: #pragma mark OscHandler
andrewm@0:
andrewm@0: OscHandler::~OscHandler()
andrewm@0: {
andrewm@0: if(oscController_ != NULL) // Remove (individually) each listener
andrewm@0: {
andrewm@0: set::iterator it;
andrewm@0:
andrewm@0: for(it = oscListenerPaths_.begin(); it != oscListenerPaths_.end(); ++it)
andrewm@0: {
andrewm@0: #ifdef DEBUG_OSC
andrewm@0: cout << "Deleting path " << *it << endl;
andrewm@0: #endif
andrewm@0:
andrewm@0: string pathToRemove = *it;
andrewm@0: oscController_->removeListener(pathToRemove, this);
andrewm@0: }
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: #pragma mark -- Private Methods
andrewm@0:
andrewm@0: // Call this internal method to add a listener to the OSC controller. Returns true on success.
andrewm@0:
andrewm@0: bool OscHandler::addOscListener(const string& path)
andrewm@0: {
andrewm@0: if(oscController_ == NULL)
andrewm@0: return false;
andrewm@0: if(oscListenerPaths_.count(path) > 0)
andrewm@0: return false;
andrewm@0: oscListenerPaths_.insert(path);
andrewm@0: oscController_->addListener(path, this);
andrewm@0: return true;
andrewm@0: }
andrewm@0:
andrewm@0: bool OscHandler::removeOscListener(const string& path)
andrewm@0: {
andrewm@0: if(oscController_ == NULL)
andrewm@0: return false;
andrewm@0: if(oscListenerPaths_.count(path) == 0)
andrewm@0: return false;
andrewm@0: oscController_->removeListener(path, this);
andrewm@0: oscListenerPaths_.erase(path);
andrewm@0: return true;
andrewm@0: }
andrewm@0:
andrewm@0: bool OscHandler::removeAllOscListeners()
andrewm@0: {
andrewm@0: if(oscController_ == NULL)
andrewm@0: return false;
andrewm@0: set::iterator it = oscListenerPaths_.begin();
andrewm@0:
andrewm@0: while(it != oscListenerPaths_.end()) {
andrewm@0: removeOscListener(*it++);
andrewm@0: }
andrewm@0:
andrewm@0: return true;
andrewm@0: }
andrewm@0:
andrewm@0: #pragma mark OscMessageSource
andrewm@0:
andrewm@0: // Adds a specific object listening for a specific OSC message. The object will be
andrewm@0: // added to the internal map from strings to objects. All messages are preceded by
andrewm@0: // a global prefix (typically "/mrp"). Returns true on success.
andrewm@0:
andrewm@9: bool OscMessageSource::addListener(const string& path, OscHandler *object, bool matchSubpath)
andrewm@0: {
andrewm@0: if(object == NULL)
andrewm@0: return false;
andrewm@0:
andrewm@0: #ifdef OLD_OSC_MESSAGE_SOURCE
andrewm@0: double before = Time::getMillisecondCounterHiRes();
andrewm@0: oscListenerMutex_.enterWrite();
andrewm@0: cout << "addListener(): took " << Time::getMillisecondCounterHiRes() - before << "ms to acquire mutex\n";
andrewm@0: noteListeners_.insert(pair(path, object));
andrewm@0: oscListenerMutex_.exitWrite();
andrewm@0: #else
andrewm@0: ScopedLock sl(oscUpdaterMutex_);
andrewm@0:
andrewm@0: // Add this object to the insertion list
andrewm@0: noteListenersToAdd_.insert(std::pair(path, object));
andrewm@0: #endif
andrewm@0:
andrewm@0: #ifdef DEBUG_OSC
andrewm@0: cout << "Added OSC listener to path '" << path << "'\n";
andrewm@0: #endif
andrewm@0:
andrewm@0: return true;
andrewm@0: }
andrewm@0:
andrewm@0: // Removes a specific object from listening to a specific OSC message.
andrewm@0: // Returns true if at least one path was removed.
andrewm@0:
andrewm@0: bool OscMessageSource::removeListener(const string& path, OscHandler *object)
andrewm@0: {
andrewm@0: if(object == NULL)
andrewm@0: return false;
andrewm@0:
andrewm@0: bool removedAny = false;
andrewm@0:
andrewm@0: #ifdef OLD_OSC_MESSAGE_SOURCE
andrewm@0: oscListenerMutex_.enterWrite(); // Lock the mutex so no incoming messages happen in the middle
andrewm@0:
andrewm@0: multimap::iterator it;
andrewm@0: pair::iterator,multimap::iterator> ret;
andrewm@0:
andrewm@0: // Every time we remove an element from the multimap, the iterator is potentially corrupted. Realistically
andrewm@0: // there should never be more than one entry with the same object and same path (we check this on insertion).
andrewm@0:
andrewm@0: ret = noteListeners_.equal_range(path);
andrewm@0:
andrewm@0: it = ret.first;
andrewm@0: while(it != ret.second)
andrewm@0: {
andrewm@0: if(it->second == object)
andrewm@0: {
andrewm@0: noteListeners_.erase(it++);
andrewm@0: removedAny = true;
andrewm@0: break;
andrewm@0: }
andrewm@0: else
andrewm@0: ++it;
andrewm@0: }
andrewm@0:
andrewm@0: oscListenerMutex_.exitWrite();
andrewm@0: #else
andrewm@0: ScopedLock sl(oscUpdaterMutex_);
andrewm@0:
andrewm@0: // Add this object to the removal list
andrewm@0: noteListenersToRemove_.insert(std::pair(path, object));
andrewm@0:
andrewm@0: // Also remove this object from anything on the add list, so it doesn't
andrewm@0: // get put back in by a previous add call.
andrewm@0: pair::iterator,multimap::iterator> ret;
andrewm@0: multimap::iterator it;
andrewm@0:
andrewm@0: ret = noteListenersToAdd_.equal_range(path);
andrewm@0: it = ret.first;
andrewm@0: while(it != ret.second) {
andrewm@0: if(it->second == object) {
andrewm@0: noteListenersToAdd_.erase(it++);
andrewm@0: //break;
andrewm@0: }
andrewm@0: else
andrewm@0: ++it;
andrewm@0: }
andrewm@0:
andrewm@0: removedAny = true; // FIXME: do we still need this?
andrewm@0: #endif
andrewm@0:
andrewm@0: #ifdef DEBUG_OSC
andrewm@0: if(removedAny)
andrewm@0: cout << "Removed OSC listener from path '" << path << "'\n";
andrewm@0: else
andrewm@0: cout << "Removal failed to find OSC listener on path '" << path << "'\n";
andrewm@0: #endif
andrewm@0:
andrewm@0: return removedAny;
andrewm@0: }
andrewm@0:
andrewm@0: // Removes an object from all OSC messages it was listening to. Returns true if object
andrewm@0: // was found and removed.
andrewm@0:
andrewm@0: bool OscMessageSource::removeListener(OscHandler *object)
andrewm@0: {
andrewm@0: if(object == NULL)
andrewm@0: return false;
andrewm@0:
andrewm@0: bool removedAny = false;
andrewm@0:
andrewm@0: #ifdef OLD_OSC_MESSAGE_SOURCE
andrewm@0: oscListenerMutex_.enterWrite(); // Lock the mutex so no incoming messages happen in the middle
andrewm@0:
andrewm@0: multimap::iterator it;
andrewm@0:
andrewm@0: // Every time we remove an element from the multimap, the iterator is potentially corrupted. Realistically
andrewm@0: // there should never be more than one entry with the same object and same path (we check this on insertion).
andrewm@0:
andrewm@0: it = noteListeners_.begin();
andrewm@0: while(it != noteListeners_.end())
andrewm@0: {
andrewm@0: if(it->second == object)
andrewm@0: {
andrewm@0: noteListeners_.erase(it++);
andrewm@0: removedAny = true;
andrewm@0: //break;
andrewm@0: }
andrewm@0: else
andrewm@0: ++it;
andrewm@0: }
andrewm@0:
andrewm@0: oscListenerMutex_.exitWrite();
andrewm@0: #else
andrewm@0: ScopedLock sl(oscUpdaterMutex_);
andrewm@0:
andrewm@0: // Add this object to the removal list
andrewm@0: noteListenersForBlanketRemoval_.insert(object);
andrewm@0:
andrewm@0: // Also remove this object from anything on the add list, so it doesn't
andrewm@0: // get put back in by a previous add call.
andrewm@0: multimap::iterator it;
andrewm@0: it = noteListenersToAdd_.begin();
andrewm@0: while(it != noteListenersToAdd_.end()) {
andrewm@0: if(it->second == object) {
andrewm@0: noteListenersToAdd_.erase(it++);
andrewm@0: }
andrewm@0: else
andrewm@0: ++it;
andrewm@0: }
andrewm@0:
andrewm@0: removedAny = true; // FIXME: do we still need this?
andrewm@0: #endif
andrewm@0:
andrewm@0: #ifdef DEBUG_OSC
andrewm@0: if(removedAny)
andrewm@0: cout << "Removed OSC listener from all paths\n";
andrewm@0: else
andrewm@0: cout << "Removal failed to find OSC listener on any path\n";
andrewm@0: #endif
andrewm@0:
andrewm@0: return removedAny;
andrewm@0: }
andrewm@0:
andrewm@0: // Propagate changes to the listeners to the main noteListeners_ object
andrewm@0:
andrewm@0: void OscMessageSource::updateListeners()
andrewm@0: {
andrewm@0: ScopedLock sl2(oscListenerMutex_);
andrewm@0: ScopedLock sl(oscUpdaterMutex_);
andrewm@0: multimap::iterator it;
andrewm@0:
andrewm@0: // Step 1: remove any objects that need complete removal from all paths
andrewm@0: set::iterator blanketRemovalIterator;
andrewm@0: for(blanketRemovalIterator = noteListenersForBlanketRemoval_.begin();
andrewm@0: blanketRemovalIterator != noteListenersForBlanketRemoval_.end();
andrewm@0: ++blanketRemovalIterator) {
andrewm@0: it = noteListeners_.begin();
andrewm@0: while(it != noteListeners_.end()) {
andrewm@0: if(it->second == *blanketRemovalIterator) {
andrewm@0: noteListeners_.erase(it++);
andrewm@0: }
andrewm@0: else
andrewm@0: ++it;
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Step 2: remove any specific path listeners
andrewm@0: for(it = noteListenersToRemove_.begin(); it != noteListenersToRemove_.end(); ++it) {
andrewm@0: pair::iterator,multimap::iterator> ret;
andrewm@0: multimap::iterator it2;
andrewm@0: string const& path = it->first;
andrewm@0: OscHandler *object = it->second;
andrewm@0:
andrewm@0: // Find all the objects that match this string and remove ones that correspond to this particular OscHandler
andrewm@0: ret = noteListeners_.equal_range(path);
andrewm@0:
andrewm@0: it2 = ret.first;
andrewm@0: while(it2 != ret.second)
andrewm@0: {
andrewm@0: if(it2->second == object) {
andrewm@0: noteListeners_.erase(it2++);
andrewm@0: //break;
andrewm@0: }
andrewm@0: else
andrewm@0: ++it2;
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Step 3: add any listeners
andrewm@0: for(it = noteListenersToAdd_.begin(); it != noteListenersToAdd_.end(); ++it) {
andrewm@0: noteListeners_.insert(pair(it->first, it->second));
andrewm@0: }
andrewm@0:
andrewm@0: // Step 4: clear the buffers of pending listeners
andrewm@0: noteListenersForBlanketRemoval_.clear();
andrewm@0: noteListenersToRemove_.clear();
andrewm@0: noteListenersToAdd_.clear();
andrewm@0: }
andrewm@0:
andrewm@0: #pragma mark OscReceiver
andrewm@0:
andrewm@0: // OscReceiver::handler()
andrewm@0: // The main handler method for incoming OSC messages. From here, we farm out the processing depending
andrewm@9: // on the path. Return 0 if the message has been adequately handled, 1 otherwise (so the server can look
andrewm@9: // for other functions to pass it to).
andrewm@0:
andrewm@0: int OscReceiver::handler(const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *data)
andrewm@0: {
andrewm@0: bool matched = false;
andrewm@0:
andrewm@0: string pathString(path);
andrewm@0:
andrewm@0: if(useThru_)
andrewm@0: {
andrewm@0: // Rebroadcast any matching messages
andrewm@0:
andrewm@0: if(!pathString.compare(0, thruPrefix_.length(), thruPrefix_))
andrewm@0: lo_send_message(thruAddress_, path, msg);
andrewm@0: }
andrewm@0:
andrewm@0: // Check if the incoming message matches the global prefix for this program. If not, discard it.
andrewm@0: if(pathString.compare(0, globalPrefix_.length(), globalPrefix_))
andrewm@0: {
andrewm@0: #ifdef DEBUG_OSC
andrewm@0: cout << "OSC message '" << path << "' received\n";
andrewm@0: #endif
andrewm@0: return 1;
andrewm@0: }
andrewm@0:
andrewm@0: // Update the list of OSC listeners to propagate any changes
andrewm@0: updateListeners();
andrewm@0:
andrewm@0: // Lock the mutex so the list of listeners doesn't change midway through
andrewm@0: oscListenerMutex_.enter();
andrewm@0:
andrewm@0: // Now remove the global prefix and compare the rest of the message to the registered handlers.
andrewm@0: multimap::iterator it;
andrewm@0: pair::iterator,multimap::iterator> ret;
andrewm@0: string truncatedPath = pathString.substr(globalPrefix_.length(),
andrewm@0: pathString.length() - globalPrefix_.length());
andrewm@9: string subpath = truncatedPath;
andrewm@0: ret = noteListeners_.equal_range(truncatedPath);
andrewm@9:
andrewm@9: while(ret.first == ret.second) {
andrewm@9: // No handlers match this range. But maybe there are higher-level handlers
andrewm@9: // that match all subpaths.
andrewm@9:
andrewm@9: // Strip off the last component of the path
andrewm@9: int pathSeparator = subpath.find_last_of('/');
andrewm@9:
andrewm@9: if(pathSeparator == string::npos) // Not found --> no match
andrewm@9: break;
andrewm@9: else {
andrewm@9: // Reduce string by one path level and add *; compare again
andrewm@9: subpath = subpath.substr(0, pathSeparator);
andrewm@9: subpath.push_back('*');
andrewm@9: ret = noteListeners_.equal_range(subpath);
andrewm@9: }
andrewm@9: }
andrewm@9:
andrewm@9: it = ret.first;
andrewm@9: while(it != ret.second) {
andrewm@9: OscHandler *object = (*it++).second;
andrewm@9:
andrewm@9: #ifdef DEBUG_OSC
andrewm@9: cout << "Matched OSC path '" << path << "' to handler " << object << endl;
andrewm@9: #endif
andrewm@9: object->oscHandlerMethod(truncatedPath.c_str(), types, argc, argv, data);
andrewm@9: matched = true;
andrewm@9: }
andrewm@0:
andrewm@0: oscListenerMutex_.exit();
andrewm@0:
andrewm@0: if(matched) // This message has been handled
andrewm@0: return 0;
andrewm@0:
andrewm@9: #ifdef DEBUG_OSC
andrewm@0: printf("Unhandled OSC path: <%s>\n", path);
andrewm@0:
andrewm@0: for (int i=0; i= addresses_.size() || index < 0)
andrewm@0: return;
andrewm@0: addresses_.erase(addresses_.begin() + index);
andrewm@0: }
andrewm@0:
andrewm@0: // Delete all destination addresses
andrewm@0:
andrewm@0: void OscTransmitter::clearAddresses()
andrewm@0: {
andrewm@0: vector::iterator it = addresses_.begin();
andrewm@0:
andrewm@0: while(it != addresses_.end()) {
andrewm@0: lo_address_free(*it++);
andrewm@0: }
andrewm@0:
andrewm@0: addresses_.clear();
andrewm@0: }
andrewm@0:
andrewm@0: void OscTransmitter::sendMessage(const char * path, const char * type, ...)
andrewm@0: {
andrewm@0: if(!enabled_)
andrewm@0: return;
andrewm@0:
andrewm@0: va_list v;
andrewm@0:
andrewm@0: va_start(v, type);
andrewm@0: lo_message msg = lo_message_new();
andrewm@0: lo_message_add_varargs(msg, type, v);
andrewm@0:
andrewm@0: /*if(debugMessages_) {
andrewm@0: cout << path << " " << type << ": ";
andrewm@0:
andrewm@0: lo_arg **args = lo_message_get_argv(msg);
andrewm@0:
andrewm@0: for(int i = 0; i < lo_message_get_argc(msg); i++) {
andrewm@0: switch(type[i]) {
andrewm@0: case 'i':
andrewm@0: cout << args[i]->i << " ";
andrewm@0: break;
andrewm@0: case 'f':
andrewm@0: cout << args[i]->f << " ";
andrewm@0: break;
andrewm@0: default:
andrewm@0: cout << "? ";
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: cout << endl;
andrewm@0: //lo_message_pp(msg);
andrewm@0: }*/
andrewm@0:
andrewm@0: sendMessage(path, type, msg);
andrewm@0:
andrewm@0: lo_message_free(msg);
andrewm@0: va_end(v);
andrewm@0: }
andrewm@0:
andrewm@0: void OscTransmitter::sendMessage(const char * path, const char * type, const lo_message& message)
andrewm@0: {
andrewm@0: if(!enabled_)
andrewm@0: return;
andrewm@0:
andrewm@0: if(debugMessages_) {
andrewm@0: cout << path << " " << type << " ";
andrewm@0:
andrewm@0: int argc = lo_message_get_argc(message);
andrewm@0: lo_arg **argv = lo_message_get_argv(message);
andrewm@0: for (int i=0; i::iterator it = addresses_.begin(); it != addresses_.end(); it++) {
andrewm@0: lo_send_message(*it, path, message);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Send an array of bytes as an OSC message. Bytes will be sent as a blob.
andrewm@0:
andrewm@0: void OscTransmitter::sendByteArray(const char * path, const unsigned char * data, int length)
andrewm@0: {
andrewm@0: if(!enabled_)
andrewm@0: return;
andrewm@0: if(length == 0)
andrewm@0: return;
andrewm@0:
andrewm@0: lo_blob b = lo_blob_new(length, data);
andrewm@0:
andrewm@0: lo_message msg = lo_message_new();
andrewm@0: lo_message_add_blob(msg, b);
andrewm@0:
andrewm@0: if(debugMessages_) {
andrewm@0: cout << path << " ";
andrewm@0: lo_message_pp(msg);
andrewm@0: }
andrewm@0:
andrewm@0: // Send message to everyone who's currently listening
andrewm@0: for(vector::iterator it = addresses_.begin(); it != addresses_.end(); it++) {
andrewm@0: lo_send_message(*it, path, msg);
andrewm@0: }
andrewm@0:
andrewm@0: lo_blob_free(b);
andrewm@0: }
andrewm@0:
andrewm@0: OscTransmitter::~OscTransmitter()
andrewm@0: {
andrewm@0: clearAddresses();
andrewm@49: }
andrewm@49:
andrewm@49: OscMessage* OscTransmitter::createMessage(const char * path, const char * type, ...)
andrewm@49: {
andrewm@49: va_list v;
andrewm@49:
andrewm@49: va_start(v, type);
andrewm@49: lo_message msg = lo_message_new();
andrewm@49: lo_message_add_varargs(msg, type, v);
andrewm@49: va_end(v);
andrewm@49:
andrewm@49: return new OscMessage(path, type, msg);
andrewm@49: }