view Source/TouchKeys/Osc.cpp @ 20:dfff66c07936

Lots of minor changes to support building on Visual Studio. A few MSVC-specific #ifdefs to eliminate things Visual Studio doesn't like. This version now compiles on Windows (provided liblo, Juce and pthread are present) but the TouchKeys device support is not yet enabled. Also, the code now needs to be re-checked on Mac and Linux.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Sun, 09 Feb 2014 18:40:51 +0000
parents f943785252fc
children 90ce403d0dc5
line wrap: on
line source
/*
  TouchKeys: multi-touch musical keyboard control software
  Copyright (c) 2013 Andrew McPherson

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
  =====================================================================
 
  Osc.cpp: classes for handling reception and transmission of OSC messages,
  using the liblo library.
*/

#include "Osc.h"

#undef DEBUG_OSC

#pragma mark OscHandler

OscHandler::~OscHandler()
{
	if(oscController_ != NULL)	// Remove (individually) each listener
	{
		set<string>::iterator it;
		
		for(it = oscListenerPaths_.begin(); it != oscListenerPaths_.end(); ++it)
		{
#ifdef DEBUG_OSC
			cout << "Deleting path " << *it << endl;
#endif
			
			string pathToRemove = *it;
			oscController_->removeListener(pathToRemove, this);
		}
	}
}

#pragma mark -- Private Methods

// Call this internal method to add a listener to the OSC controller.  Returns true on success.

bool OscHandler::addOscListener(const string& path)
{
	if(oscController_ == NULL)
		return false;
	if(oscListenerPaths_.count(path) > 0)
		return false;
	oscListenerPaths_.insert(path);
	oscController_->addListener(path, this);
	return true;
}

bool OscHandler::removeOscListener(const string& path)
{
	if(oscController_ == NULL)
		return false;
	if(oscListenerPaths_.count(path) == 0)
		return false;
	oscController_->removeListener(path, this);
	oscListenerPaths_.erase(path);
	return true;
}

bool OscHandler::removeAllOscListeners()
{
	if(oscController_ == NULL)
		return false;
	set<string>::iterator it = oscListenerPaths_.begin();
	
	while(it != oscListenerPaths_.end()) {
		removeOscListener(*it++);
	}
	
	return true;
}

#pragma mark OscMessageSource

// Adds a specific object listening for a specific OSC message.  The object will be
// added to the internal map from strings to objects.  All messages are preceded by
// a global prefix (typically "/mrp").  Returns true on success.

bool OscMessageSource::addListener(const string& path, OscHandler *object, bool matchSubpath)
{
	if(object == NULL)
		return false;
    
#ifdef OLD_OSC_MESSAGE_SOURCE
	double before = Time::getMillisecondCounterHiRes();
	oscListenerMutex_.enterWrite();
    cout << "addListener(): took " << Time::getMillisecondCounterHiRes() - before << "ms to acquire mutex\n";
	noteListeners_.insert(pair<string, OscHandler*>(path, object));
	oscListenerMutex_.exitWrite();
#else
    ScopedLock sl(oscUpdaterMutex_);
    
    // Add this object to the insertion list
    noteListenersToAdd_.insert(std::pair<string, OscHandler*>(path, object));
#endif
    
#ifdef DEBUG_OSC
	cout << "Added OSC listener to path '" << path << "'\n";
#endif
	
	return true;
}

// Removes a specific object from listening to a specific OSC message.
// Returns true if at least one path was removed.

bool OscMessageSource::removeListener(const string& path, OscHandler *object)
{
	if(object == NULL)
		return false;
	
	bool removedAny = false;
	
#ifdef OLD_OSC_MESSAGE_SOURCE    
	oscListenerMutex_.enterWrite(); // Lock the mutex so no incoming messages happen in the middle
	
	multimap<string, OscHandler*>::iterator it;
	pair<multimap<string, OscHandler*>::iterator,multimap<string, OscHandler*>::iterator> ret;
	
	// Every time we remove an element from the multimap, the iterator is potentially corrupted.  Realistically
	// there should never be more than one entry with the same object and same path (we check this on insertion).
	
	ret = noteListeners_.equal_range(path);
	
	it = ret.first;
	while(it != ret.second)
	{
		if(it->second == object)
		{
			noteListeners_.erase(it++);
			removedAny = true;
			break;
		}
		else
			++it;
	}
	
	oscListenerMutex_.exitWrite();
#else
    ScopedLock sl(oscUpdaterMutex_);
    
    // Add this object to the removal list
    noteListenersToRemove_.insert(std::pair<string, OscHandler*>(path, object));
    
    // Also remove this object from anything on the add list, so it doesn't
    // get put back in by a previous add call.
    pair<multimap<string, OscHandler*>::iterator,multimap<string, OscHandler*>::iterator> ret;
    multimap<string, OscHandler*>::iterator it;
    
    ret = noteListenersToAdd_.equal_range(path);
    it = ret.first;
    while(it != ret.second) {
        if(it->second == object) {
            noteListenersToAdd_.erase(it++);
            //break;
        }
        else
            ++it;
    }
    
    removedAny = true; // FIXME: do we still need this?
#endif
    
#ifdef DEBUG_OSC
	if(removedAny)
		cout << "Removed OSC listener from path '" << path << "'\n";	
	else
		cout << "Removal failed to find OSC listener on path '" << path << "'\n";
#endif
	
	return removedAny;
}

// Removes an object from all OSC messages it was listening to.  Returns true if object
// was found and removed.

bool OscMessageSource::removeListener(OscHandler *object)
{
	if(object == NULL)
		return false;

	bool removedAny = false;

#ifdef OLD_OSC_MESSAGE_SOURCE
	oscListenerMutex_.enterWrite();	// Lock the mutex so no incoming messages happen in the middle
	
	multimap<string, OscHandler*>::iterator it;

	// Every time we remove an element from the multimap, the iterator is potentially corrupted.  Realistically
	// there should never be more than one entry with the same object and same path (we check this on insertion).
	
	it = noteListeners_.begin();
	while(it != noteListeners_.end())
	{
		if(it->second == object)
		{
			noteListeners_.erase(it++);
			removedAny = true;
			//break;
		}
		else
			++it;
	}
	
	oscListenerMutex_.exitWrite();
#else
    ScopedLock sl(oscUpdaterMutex_);
    
    // Add this object to the removal list
    noteListenersForBlanketRemoval_.insert(object);
    
    // Also remove this object from anything on the add list, so it doesn't
    // get put back in by a previous add call.
    multimap<string, OscHandler*>::iterator it;
    it = noteListenersToAdd_.begin();
    while(it != noteListenersToAdd_.end()) {
        if(it->second == object) {
            noteListenersToAdd_.erase(it++);
        }
        else
            ++it;
    }
    
    removedAny = true; // FIXME: do we still need this?
#endif
	
#ifdef DEBUG_OSC
	if(removedAny)
		cout << "Removed OSC listener from all paths\n";	
	else
		cout << "Removal failed to find OSC listener on any path\n";
#endif
	
	return removedAny;
}

// Propagate changes to the listeners to the main noteListeners_ object

void OscMessageSource::updateListeners()
{
    ScopedLock sl2(oscListenerMutex_);    
    ScopedLock sl(oscUpdaterMutex_);
	multimap<string, OscHandler*>::iterator it;
    
    // Step 1: remove any objects that need complete removal from all paths
    set<OscHandler*>::iterator blanketRemovalIterator;
    for(blanketRemovalIterator = noteListenersForBlanketRemoval_.begin();
        blanketRemovalIterator != noteListenersForBlanketRemoval_.end();
        ++blanketRemovalIterator) {
        it = noteListeners_.begin();
        while(it != noteListeners_.end()) {
            if(it->second == *blanketRemovalIterator) {
                noteListeners_.erase(it++);
            }
            else
                ++it;
        }
    }
    
    // Step 2: remove any specific path listeners
    for(it = noteListenersToRemove_.begin(); it != noteListenersToRemove_.end(); ++it) {
        pair<multimap<string, OscHandler*>::iterator,multimap<string, OscHandler*>::iterator> ret;
        multimap<string, OscHandler*>::iterator it2;
        string const& path = it->first;
        OscHandler *object = it->second;
        
        // Find all the objects that match this string and remove ones that correspond to this particular OscHandler
        ret = noteListeners_.equal_range(path);
        
        it2 = ret.first;
        while(it2 != ret.second)
        {
            if(it2->second == object) {
                noteListeners_.erase(it2++);
                //break;
            }
            else
                ++it2;
        }
    }

    // Step 3: add any listeners
    for(it = noteListenersToAdd_.begin(); it != noteListenersToAdd_.end(); ++it) {
        noteListeners_.insert(pair<string, OscHandler*>(it->first, it->second));
    }
    
    // Step 4: clear the buffers of pending listeners
    noteListenersForBlanketRemoval_.clear();
    noteListenersToRemove_.clear();
    noteListenersToAdd_.clear();
}

#pragma mark OscReceiver

// OscReceiver::handler()
// The main handler method for incoming OSC messages.  From here, we farm out the processing depending
// on the path. Return 0 if the message has been adequately handled, 1 otherwise (so the server can look
// for other functions to pass it to).

int OscReceiver::handler(const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *data)
{
	bool matched = false;
	
	string pathString(path);	
	
	if(useThru_)
	{
		// Rebroadcast any matching messages
		
		if(!pathString.compare(0, thruPrefix_.length(), thruPrefix_))
			lo_send_message(thruAddress_, path, msg);
	}
	
	// Check if the incoming message matches the global prefix for this program.  If not, discard it.
	if(pathString.compare(0, globalPrefix_.length(), globalPrefix_))
	{
#ifdef DEBUG_OSC
		cout << "OSC message '" << path << "' received\n";
#endif
		return 1;
	}
	
    // Update the list of OSC listeners to propagate any changes
    updateListeners();
    
	// Lock the mutex so the list of listeners doesn't change midway through
    oscListenerMutex_.enter();
	
	// Now remove the global prefix and compare the rest of the message to the registered handlers.
	multimap<string, OscHandler*>::iterator it;
	pair<multimap<string, OscHandler*>::iterator,multimap<string, OscHandler*>::iterator> ret;
	string truncatedPath = pathString.substr(globalPrefix_.length(), 
											 pathString.length() - globalPrefix_.length());
    string subpath = truncatedPath;
	ret = noteListeners_.equal_range(truncatedPath);

    while(ret.first == ret.second) {
        // No handlers match this range. But maybe there are higher-level handlers
        // that match all subpaths.
        
        // Strip off the last component of the path
        int pathSeparator = subpath.find_last_of('/');

        if(pathSeparator == string::npos)   // Not found --> no match
            break;
        else {
            // Reduce string by one path level and add *; compare again
            subpath = subpath.substr(0, pathSeparator);
            subpath.push_back('*');
            ret = noteListeners_.equal_range(subpath);
        }
    }

    it = ret.first;
    while(it != ret.second) {
        OscHandler *object = (*it++).second;
        
#ifdef DEBUG_OSC
        cout << "Matched OSC path '" << path << "' to handler " << object << endl;
#endif
        object->oscHandlerMethod(truncatedPath.c_str(), types, argc, argv, data);
        matched = true;
    }
	
    oscListenerMutex_.exit();
    
	if(matched)		// This message has been handled
		return 0;
	
#ifdef DEBUG_OSC    
	printf("Unhandled OSC path: <%s>\n", path);
	
    for (int i=0; i<argc; i++) {
		printf("arg %d '%c' ", i, types[i]);
		lo_arg_pp((lo_type)types[i], argv[i]);
		printf("\n");
    }
#endif
	
    return 1;
}

// Set the current port for the OSC receiver object. This implies stopping and
// restarting the server. Returns true on success.
bool OscReceiver::setPort(const int port)
{
    // Stop existing server if running
    if(oscServerThread_ != 0) {
        lo_server_thread_del_method(oscServerThread_, NULL, NULL);
        lo_server_thread_stop(oscServerThread_);
        lo_server_thread_free(oscServerThread_);
        oscServerThread_ = 0;
    }
    
    // Port value 0 indicates to turn off; this always succeeds.
    if(port == 0) {
        return true;
    }
    
    // Now create a new one on the new port
    char portStr[16];
#ifdef _MSC_VER
	_snprintf_s(portStr, 16, _TRUNCATE, "%d", port);
#else
    snprintf(portStr, 16, "%d", port);
#endif

    oscServerThread_ = lo_server_thread_new(portStr, staticErrorHandler);
    if(oscServerThread_ != 0) {
        lo_server_thread_add_method(oscServerThread_, NULL, NULL, OscReceiver::staticHandler, (void *)this);
        lo_server_thread_start(oscServerThread_);
        return true;
    }
    
    return false;
}

#pragma mark OscTransmitter

// Add a new transmit address.  Returns the index of the new address.

int OscTransmitter::addAddress(const char * host, const char * port, int proto)
{
	lo_address addr = lo_address_new_with_proto(proto, host, port);
	
	if(addr == 0)
		return -1;
	addresses_.push_back(addr);
	
	return (int)addresses_.size() - 1;
}

// Delete a current transmit address

void OscTransmitter::removeAddress(int index)
{
	if(index >= addresses_.size() || index < 0)
		return;
	addresses_.erase(addresses_.begin() + index);
}

// Delete all destination addresses

void OscTransmitter::clearAddresses()
{
	vector<lo_address>::iterator it = addresses_.begin();
	
	while(it != addresses_.end()) {
		lo_address_free(*it++);
	}
	
	addresses_.clear();
}

void OscTransmitter::sendMessage(const char * path, const char * type, ...)
{
    if(!enabled_)
        return;
    
	va_list v;
	
	va_start(v, type);
	lo_message msg = lo_message_new();
	lo_message_add_varargs(msg, type, v);

	/*if(debugMessages_) {
		cout << path << " " << type << ": ";
		
		lo_arg **args = lo_message_get_argv(msg);
		
		for(int i = 0; i < lo_message_get_argc(msg); i++) {
			switch(type[i]) {
				case 'i':
					cout << args[i]->i << " ";
					break;
				case 'f':
					cout << args[i]->f << " ";
					break;
				default:
					cout << "? ";
			}
		}
		
		cout << endl;
		//lo_message_pp(msg);
	}*/
	
	sendMessage(path, type, msg);

	lo_message_free(msg);
	va_end(v);
}

void OscTransmitter::sendMessage(const char * path, const char * type, const lo_message& message)
{
    if(!enabled_)
        return;
    
    if(debugMessages_) {
        cout << path << " " << type << " ";

        int argc = lo_message_get_argc(message);
        lo_arg **argv = lo_message_get_argv(message);
        for (int i=0; i<argc; i++) {
            lo_arg_pp((lo_type)type[i], argv[i]);
            cout << " ";
        }
        cout << endl;
    }
    
	// Send message to everyone who's currently listening
	for(vector<lo_address>::iterator it = addresses_.begin(); it != addresses_.end(); it++) {
		lo_send_message(*it, path, message);
	}
}

// Send an array of bytes as an OSC message.  Bytes will be sent as a blob.

void OscTransmitter::sendByteArray(const char * path, const unsigned char * data, int length)
{
    if(!enabled_)
        return;
	if(length == 0)
		return;
	
	lo_blob b = lo_blob_new(length, data);
	
	lo_message msg = lo_message_new();
	lo_message_add_blob(msg, b);
	
	if(debugMessages_) {
		cout << path << " ";
		lo_message_pp(msg);
	}
	
	// Send message to everyone who's currently listening
	for(vector<lo_address>::iterator it = addresses_.begin(); it != addresses_.end(); it++) {
		lo_send_message(*it, path, msg);
	}	
	
	lo_blob_free(b);
}

OscTransmitter::~OscTransmitter()
{
	clearAddresses();
}