September 1, 2015 Tom

Grabbing data from MuseIO: A few simple examples of Muse OSC Servers

osc_server_examples

The Muse Tools use Open Sound Control (OSC) to pass data around. OSC is a simple protocol for sending data over a network. It was originally intended as a successor to MIDI, the well-known protocol for controlling electronic instruments, but it turns out to be really useful for all sorts of things, including Muse data.

If you’ve followed the Tools Getting Started guide for your OS you know that the way things work is like this: you connect to Muse with MuseDirect or MuseIO and then the tool you connected with starts sending out tons of data over OSC. In the Getting Started, the next step was to run MuseLab to receive and graph it all.

But what if you want to get that OSC data into your own program? You need an OSC server – something to listen for and handle incoming messages. Luckily, there are OSC libraries for seemingly every major programming language, so you just need to find the right one and include it in your project.

Let’s look at a few simple examples of OSC servers written in different languages. We’ll expand on these in future posts, but for now let’s get a handle on simply receiving data.

For each of these, you need to connect to your Muse first and start spewing out messages on a known port like 5000 or 7000. If you are using MuseIO, you will want to use a command like the following command to connect to your Muse.


muse-io --device Muse-XXXX --osc osc.udp://localhost:5000

Then you tell your server to listen on that same port, and start receiving and parsing messages.

All right, enough chit chat – to the examples!

C – liblo (Mac/Linux only)

Download Example

liblo is one of the most popular OSC libraries for POSIX systems (meaning Mac and Linux, basically. Using liblo on Windows is a pain, as it is not compiled to use Win32 threads). This example is adapted directly from the official example OSC server included with liblo, modified to print out Muse EEG OSC messages.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "lo/lo.h"

int done = 0;

void error(int num, const char *m, const char *path);

int generic_handler(const char *path, const char *types, lo_arg ** argv,
                    int argc, void *data, void *user_data);

int eeg_handler(const char *path, const char *types, lo_arg ** argv,
                int argc, void *data, void *user_data);

int quit_handler(const char *path, const char *types, lo_arg ** argv,
                 int argc, void *data, void *user_data);

int main()
{
    /* start a new server on port 5000 */
    lo_server_thread st = lo_server_thread_new("5000", error);

    /* add method that will match any path and args */
    lo_server_thread_add_method(st, NULL, NULL, generic_handler, NULL);

    /* add method that will match the path /muse/eeg with four floats */
    lo_server_thread_add_method(st, "/muse/eeg", "ffff", eeg_handler, NULL);

    /* add method that will match the path /quit with no args */
    lo_server_thread_add_method(st, "/quit", "", quit_handler, NULL);

    lo_server_thread_start(st);

    while (!done) {
    #ifdef WIN32
        Sleep(1);
    #else
        usleep(1000);
    #endif
    }

    lo_server_thread_free(st);

    return 0;
}

void error(int num, const char *msg, const char *path)
{
    printf("liblo server error %d in path %s: %s\n", num, path, msg);
    fflush(stdout);
}

/* catch any incoming messages and display them. returning 1 means that the
 * message has not been fully handled and the server should try other methods */
int generic_handler(const char *path, const char *types, lo_arg ** argv,
                    int argc, void *data, void *user_data)
{
    int i;

    printf("path: <%s>\n", path);
    for (i = 0; i < argc; i++) {
        printf("arg %d '%c' ", i, types[i]);
        lo_arg_pp((lo_type)types[i], argv[i]);
        printf("\n");
    }
    printf("\n");
    fflush(stdout);

    return 1;
}

int eeg_handler(const char *path, const char *types, lo_arg ** argv,
                int argc, void *data, void *user_data)
{
    /* example showing pulling the argument values out of the argv array */
    printf("%s f, argv[1]->f, argv[2]->f, argv[3]->f);
    fflush(stdout);

    return 0;
}

int quit_handler(const char *path, const char *types, lo_arg ** argv,
                 int argc, void *data, void *user_data)
{
    done = 1;
    printf("quiting\n\n");
    fflush(stdout);

    return 0;
}

C#

Download Example

This example uses the SharpOSC library. It is simply a modified version of the receiver example included in the SharpOSC README. Don’t forget to reference the SharpOSC.dll file in your project! It is located at muse_osc_server/bin/SharpOSC.dll. You can also grab the latest version from the SharpOSC github repo.


using System;
using SharpOSC;

namespace muse_osc_server
{
	class MainClass
	{
		public static void Main(string[] args)
		{
			// Callback function for received OSC messages. 
			// Prints EEG and Relative Alpha data only.
			HandleOscPacket callback = delegate(OscPacket packet)
			{
				var messageReceived = (OscMessage)packet;
				var addr = messageReceived.Address;
				if(addr == "/muse/eeg") {
					Console.Write("EEG values: ");
					foreach(var arg in messageReceived.Arguments) {
						Console.Write(arg + " ");
					}
				}
				if(addr == "/muse/elements/alpha_relative") {
					Console.Write("Relative Alpha power values: ");
					foreach(var arg in messageReceived.Arguments) {
						Console.Write(arg + " ");
					}
				}
			};

			// Create an OSC server.
			var listener = new UDPListener(5000, callback);

			Console.WriteLine("Press enter to stop");
			Console.ReadLine();
			listener.Close();
		}
	}
}

Java - oscP5

Download Example

This example uses oscP5, an OSC library that is mainly used with the Processing programming language, but since Processing is built on Java, is also compatible with Java.


package example;

import oscP5.*;

public class MuseOscServer {

	static MuseOscServer museOscServer;
	
	OscP5 museServer;
	static int recvPort = 5000;

	public static void main(String [] args) {
		museOscServer = new MuseOscServer();
		museOscServer.museServer = new OscP5(museOscServer, recvPort);
			}
	
	void oscEvent(OscMessage msg) {
		System.out.println("### got a message " + msg);
		if (msg.checkAddrPattern("/muse/eeg")==true) {  
			for(int i = 0; i < 4; i++) {
				System.out.print("EEG on channel " + i + ": " + msg.get(i).floatValue() + "\n"); 
			}
		} 
	}
}

Processing - oscP5

Download Example

Processing is a great tool for quickly building interactive visualizations and it has solid OSC support. Check out openprocessing.org for some examples of the awesome things that you can build with it.

Like the Java example, this code uses oscP5. It's very similar in to the Java code - as one might expect - with a few extra Processing-specific details, such as the setup() and draw() functions (although these have nothing to do with receiving the OSC data, they're just necessary components of any Processing sketch).

  
  import oscP5.*;
  
  // OSC PARAMETERS & PORTS
  int recvPort = 5000;
  OscP5 oscP5;
  
  // DISPLAY PARAMETERS
  int WIDTH = 100;
  int HEIGHT = 100;
  
  void setup() {
    size(WIDTH,HEIGHT);
    frameRate(60);
    
    /* start oscP5, listening for incoming messages at recvPort */
    oscP5 = new OscP5(this, recvPort);
    background(0);
  }

  void draw() {
    background(0);
  }
  
  void oscEvent(OscMessage msg) {
	System.out.println("### got a message " + msg);
	if (msg.checkAddrPattern("/muse/eeg")==true) {  
		for(int i = 0; i < 4; i++) {
			System.out.print("EEG on channel " + i + ": " + msg.get(i).floatValue() + "\n"); 
		}
	} 
  }

Python

There are several OSC libraries for Python. However, be careful when choosing. The best choice depends on what platform you're working on and which version of Python you use. For instance, pyliblo targets both Python 2.x and 3.x but only works well on Mac or Linux. On the other hand, python-osc works well on all platforms but targets Python 3.x. So keep these constraints in mind when choosing an OSC library for Python.

pyliblo (Mac/Linux only)

Download Example

pyliblo works best on Mac and Linux because it is a wrapper for liblo, which uses POSIX threads. You could potentially recompile liblo for Win32 threads, but it is a tedious process.

from liblo import *

import sys 
import time


class MuseServer(ServerThread):
    #listen for messages on port 5000
    def __init__(self):
        ServerThread.__init__(self, 5000)

    #receive accelrometer data
    @make_method('/muse/acc', 'fff')
    def acc_callback(self, path, args):
        acc_x, acc_y, acc_z = args
        print "%s %f %f %f" % (path, acc_x, acc_y, acc_z)

    #receive EEG data
    @make_method('/muse/eeg', 'ffff')
    def eeg_callback(self, path, args):
        l_ear, l_forehead, r_forehead, r_ear = args
        print "%s %f %f %f %f" % (path, l_ear, l_forehead, r_forehead, r_ear)

    #handle unexpected messages
    @make_method(None, None)
    def fallback(self, path, args, types, src):
        print "Unknown message \
        \n\t Source: '%s' \
        \n\t Address: '%s' \
        \n\t Types: '%s ' \
        \n\t Payload: '%s'" \
        % (src.url, path, types, args)

try:
    server = MuseServer()
except ServerError, err:
    print str(err)
    sys.exit()


server.start()

if __name__ == "__main__":
    while 1:
        time.sleep(1)

python-osc

Download Example

NOTE: python-osc targets Python 3.x only. It does not work with Python 2.x (which is the default on Mac OS X).


import argparse
import math

from pythonosc import dispatcher
from pythonosc import osc_server


def eeg_handler(unused_addr, args, ch1, ch2, ch3, ch4):
    print("EEG (uV) per channel: ", ch1, ch2, ch3, ch4)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip",
                        default="127.0.0.1",
                        help="The ip to listen on")
    parser.add_argument("--port",
                        type=int,
                        default=5000,
                        help="The port to listen on")
    args = parser.parse_args()

    dispatcher = dispatcher.Dispatcher()
    dispatcher.map("/debug", print)
    dispatcher.map("/muse/eeg", eeg_handler, "EEG")

    server = osc_server.ThreadingOSCUDPServer(
        (args.ip, args.port), dispatcher)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()

Max/MSP

Download Example

To connect Muse data to visualizations and sound inside Max/MSP you can use the udpreceive object and the OSC-route library as shown below.

maxmsp_osc

That's all for now! Want to see an example for another language? Let us know on the forums and we'll try to add it!

Coming soon: we'll take a look at how to build on some of these examples to create visualizations that respond to Muse data. Stay tuned!

Tagged: , , , , , , , , , , , , ,