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: }