ProctoLogic: User Manual

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Daniel Holth

Clinton McChesney

 

 

 


Introducing ProctoLogic

 

 


Thank you for choosing the award-winning ProctoLogic. With this software you will find it easy to add networking to your multimedia projects. ProctoLogic makes it easy to distribute information amongst a network of computers. With practice, you can create complex artistic collaborations, inspiring awe in your friends and envy in your competitors.

 

About This Manual

 

The ProctoLogic User’s Manual consists of 4 chapters.  Each chapter will provide information about a different component of ProctoLogic.  Along with easy-to-follow steps written in plain English, sample code is provided to demonstrate explicitly how to implement different features. 

 

In addition to simply teaching you how to use each piece of ProctoLogic, this manual will also discuss how ProctoLogic works.  This knowledge is provided to help you, should you wish to expand ProctoLogic, or create your own pieces.

 

§         Chapter 1, “What IS ProctoLogic?” will give you an understanding of exaclty what ProctoLogic is, where it comes from, and where it is going.

§         Chapter 2, “Clients” shows how to create both input and output clients, or how to send and receive data using ProctoLogic.

§         Chapter 3, “Servers” discusses what is necessary to set up your own ProctoLogic server, and how to use an existing server.

§         Chapter 4, “Utilities” is a chapter that details some utilities included with this package, and how they can be used with your own projects.

 

Documentation conventions

 

The documentation in this manual follows a convention that will make sample code easy to distinguish from explanatory text, as well as making it easy to read:

 

Standard text is shown in Times New Roman, and is the manual’s primary way of conveying instructions or explanations to the reader.

 

Example code, or text that is expected to be displayed on your monitor, will be written in Courier.  Sample code will also be highlighted. 

 

            This is an example of text.

      

// This is example code.

subscriber = new Subscriber( );

 1

 
What IS ProctoLogic?

 

 

This chapter discusses what ProctoLogic is, where it came from, and where it is going.  You will learn exactly what the creators of ProctoLogic had in mind when they created it, and how ProctoLogic goes about accomplishing these goals. 

 

Description

 

Put simply, ProctoLogic is a tool that will allow artists (such as yourself) to create collaborative pieces of net art with other artists (such as your friends) by allowing them to communicate with one another. Put complexly, ProctoLogic is a network of computers connected to a central server that is responsible for connecting a number of input clients to a number of output clients.  It is your job as an artist to create these clients, or to extend existing clients to fit your own artistic needs.
 
ProctoLogic was intended to be an easy-to-use tool that would allow artists to connect multiple computers together for a collaborative experience.  By hiding away the complications and complexities that are associated with computer networking and data transmission, ProctoLogic leaves artists and users to focus more on the interpretation of the data.  This allows artists to be artists, not programmers.  
 
ProctoLogic uses a protocol known as Open SoundControl to send data across a network.  In fact, the creators of ProctoLogic have written their own version of Open SoundControl that is compatible with other versions, but is much easier to use.  This new version of Open SoundControl will allow the user to specify what type of data is being sent, as well as providing retrievable documentation about the information.  
                                                                                
Once an artist or a group of artists have created an interesting way of gathering or interpreting data, ProctoLogic can be used to make sure that the computers or devices responsible for the data can communicate with one another.  The data interpretation need not be limited to just art, but elaborate performances, or recreational uses such as games.
 
Background

 

The root of ProctoLogic is in the Stetho project.  The Stetho project was a diagnostic tool that a network analyst or administrator could use to monitor network traffic.  Stetho would use a program called tcpdump to collect information about the network traffic, then parse through that information.  Based upon the information, Stetho would play certain .wav files, or make MIDI noises.  Stetho was meant to be a “network auralizer,” or a tool that would allow people to listen to network traffic data rather than look at large printouts.

 

Projects similar to Stetho were also studied, and it was decided that ProctoLogic should be able to perform the same functions as these tools, but also be used for artistic purposes.  Projects focusing on net art were then studied, such as the W server.  From this research, the protocol Open SoundControl was discovered and it was instantly decided that ProctoLogic should make use of it. 

 

ProctoLogic has now grown from a simple way for one computer to talk to another into the advanced architecture that it is, today.  ProctoLogic can now allow multiple computers to talk to multiple computers.  A central server can reside in the middle, monitoring its clients, decoupling the inputs from the outputs, and ensuring that no client will hog the network.

 

Uses

 

ProctoLogic has many uses.  It is still capable of being a diagnostic tool, and can be used for a variety of other reasons.  Once an artist becomes interested in a type of data, then ProctoLogic can be used to help that artist send this data to other artists that are interested in interpreting it.  ProctoLogic can also be used to help control applications over a network.  Games can also use ProctoLogic as a means of communication.  Whenever a multitude of computers need to share and transmit data, ProctoLogic can be used.

 

Future

 

The goal of ProctoLogic is still to be an intuitive, easy-to-use tool for net artists.  The future of ProctoLogic will hopefully contain an intuitive graphic interface for artists to use.  Additionally, once more and more artists get hold of ProctoLogic, then more interesting ways of gathering or interpreting data can be created.  ProctoLogic servers will become more powerful, making ProctoLogic an even more alluring tool for net artists around the world.

 


 2

 
       Clients

 

 

So you want to create a client for ProctoLogic?  This chapter will discuss exactly what a client for ProctoLogic is and what it does.  You will learn about input clients, output clients, and the difference between them.  In addition, you will be shown how to create your own.  Example code will be given to make this a relatively easy and painless process for you. Now, read on to discover what you need to do to make ProctoLogic work for you!

 

This chapter is divided into two main parts:

 

§         Section 1, “Input Clients” will discuss what an input client is, and how to make one.  Implementations in Python will be discussed.

§         Section 2, “Output Clients” will discuss what an output client is, and how to make one.  Implementations in C++ will be discussed.

 

Fully executable example code will is provided with your ProctoLogic kit.  Also, see the Programmer’s Manual for a complete reference of every object that we use.

 

Input Clients

 

An input client is responsible for collecting or gathering data. Once an input client has its data, it can send it to a server or an output client.  How the data is collected, as well as the type of data collected is up to the user or artist.  Mainly, an input client will simply harvest data, pack it into an Open SoundControl message, then send it to a listening server or client.

 

Input Clients in Python

This simple input client gets lines of text from the user, and sends them to a specified server.

 

The following imports will be needed.

import OSC
import socket
import sys
 
 
The following method gets text, packs that text into an OSC message, then sends it to the server specified by address.
 
def run(remote, address):
 
The socket that we are going to use for sending.
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 
    print "sending lines to", remote, "(osc: %s)" % address
Here we receive text inside a loop.  Once the text is obtained, we pack it into an OSC message.  To do this, we create a new message, and set its address to the name of the channel on the server or output client that is expecting data. Then, we begin appending our data to the message.  Finally, in order to send via UDP, we get the binary value of the message and send it to the location of the server, specified by the argument, remote.
 
    message = "foo"
    while message != None:
        try:
            message = raw_input()
        except EOFError, e:
            print "bye..."
            sys.exit(0)
 
        osc = OSC.OSCMessage()
        osc.setAddress(address)
        osc.append(message)
 
        data = osc.getBinary()
        s.sendto(data, remote)
 
Now, in the main method of the input client, we are specifying the name of the channel and the location of the server.  
 
if __name__ == "__main__":
    arguments = sys.argv[1:]
    arguments.reverse()
 
    port        = 4950
    hostname    = None
    address     = "/print"
 
    while arguments:
        argument = arguments.pop()
        if argument == "-p":
            try:
                port = arguments.pop()
                port = int(port)
            except:
                sys.stdout.write("Error getting port number")
                usage()
                raise
        elif argument in ("--help", "-h"):
            usage()
            sys.exit()
        elif hostname == None:
            hostname = argument
        else:
            address = argument
            
 
    if port == None:
        print "Please give a port number."
    if hostname == None:
        print "Please give a hostname."
    if address == None:
        print "Please give an address."
 
    if None in (address, hostname, port):
        usage()
        sys.exit()
     
    run((hostname, port), address)

 

Output Clients

 

The output client listens, waiting for data to be sent to it.  Once it receives the data, it can do a variety of things with it.  What is done is entirely up to the user or artist.  The output client is where the artist has free reign to create interesting data interpretations.  The data received will be in the form of an Open SoundControl message.  Certain steps will be necessary to get the desired data from the message.  In C++, you will have to extend the OSCCallable class to deal with and interpret the data.  Refer to the examples.

 

An output client can either receive information directly from an input client, or it can subscribe to a server.  Subscribing to a server is covered in Chapter 3, “Servers” in the “Subscribing to an Existing Server” section.

 

Output Clients in C++

Once you have decided how you are going to interpret data, and have written your extension to the OSCCallable, you can begin creating your C++ output client.  This client does not subscribe to a channel on a server.  It simply uses a UDP socket to listen for messages.  The following steps outline how to do this, with example code showing you the specifics:
 
You must first begin by importing some libraries that should be included with your C++ compiler.  These libraries are mainly used to deal with networking, threading, and data structures.
 
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
 
The second set of included files is included in the ProctoLogic kit that you downloaded.  They deal with Open SoundControl messages and namespaces.
 
#include "MapServiceManager.h"
#include "OSCAssociativeNamespace.h"
#include "OSCCallable.h"
#include "OSCDocumenter.h"
#include "OSCLister.h"
#include "OSCPrintCallable.h"
#include "OSCProcessor.h"
#include "OSCReplyCallable.h"
#include "OSCTypeReturner.h"
#include "standards.h"
#include "UDPService.h"
 
You will need to create a variety of objects.  Most of these objects are the same files that you just included.  They will build your Open SoundControl namespace, and allow you to listen for UDP messages. 
 

bool success;

int  portNumber;

 

MapServiceManager *m;

OSCCallable       *callback;

OSCDocumentor     *documentman;

OSCLister         *lister;

OSCNamespace      *name;

OSCProcessor      *processor;

OSCTypeReturner   *typeReturner;

UDPService        *listener;
 

m         = new MapServiceManager();        // Keeps track of potentially

                                            // many listening sockets,

                                            // using select()

name      = new OSCAssociativeNamespace();  // Maps callback names to objects

processor = new OSCProcessor();             // Dispatches incoming messages

listener  = new UDPService();               // Recieves data from UDP

processor -> setNamespace(name);            // Looks for callbacks in

                                            // this namespace

listener  -> setProcessor(processor);       // Sends recieved UDP messages

                                            // to processor

portNumber  = PORT;                         // PORT defined in standards.h

success     = listener->init(portNumber);   // Create a listening UDP socket

 

Now that you have the objects you need, it’s time to start listening for UDP messages.  You don’t have to do the error checking like we have done, but it’s recommended.

 

if(!success)

{

  cerr << "Couldn't initialize the UDP listener." << endl;

  exit(1);

}

m->addService(listener);

 

Next, we are going to set up some of the services that ProctoLogic contains.  These include the ability to list everything in the namespace, and give documentation to someone that requests it.  You will see that once the service has been created, it is added to the namespace using name -> add().  When this function is called, it means that the callable object will be called when a message is sent to the address specified.

 

lister = new OSCLister();       // Handles listing queries

lister -> setNamespace(name);   // Returns addresses from this namespace

name -> add("/list", lister);   // Now, it's available as '/list'.

 

documentman = new OSCDocumentor();    // Handles documentation requests

documentman -> setNamespace(name);    // Documents objects in this namespace

documentman -> setDocumentation("Take a string (an OSC address) as an argument and

return documentation for that address."); // It is itself documented.

name -> add("/document", documentman);

 

       typeReturner = new OSCTypeReturner(); // Handles type signature queries

typeReturner -> setNamespace(name);

typeReturner -> setDocumentation("Take the OSC address of a callback and return

its address and type signature.");

name -> add("/typeSignature", typeReturner);

 

Finally, it’s time to add the ojbect that is responsible for interpreting data.  The ojbect that we have created in this example simply receives the data, then prints it out.

 

callback = new OSCPrintCallable("Hello.");

  callback -> setTypeSignature("s");  // This callback expects a single string.

                                      // Omit the magic comma here.

                                      // This callback doesn't really care

                                      // about the types of its arguments; it

                                      // just prints its data out verbatim.

 

  callback -> setDocumentation("Print a message on the server when called.");

 

  name->add("/print", callback);      // Now we can send messages to '/print'

                                      // on this server, and this callback's

                                      // call(string, Transmit*) method is

                                      // called with the OSC message we sent

                                      // and an object that may be used to

                                      // reply.

 

 

  m->serveForever();                  // Wait for UDP packets on the

                                      // default port.  Does not return.

 


 3

 
       Servers

 

 

The ProctoLogic server is a very powerful thing.  In this chapter, you will discover how to build one for yourself.  If you already have a server, this chapter will also discuss how to subscribe to a pre-existing server.  We will show you an example of a simple C++ server. 

 

The server exists mainly to decouple the input clients from the output clients, making it unnecessary for one to know about the other.  Essentially, a server is nothing more than a ProctoLogic application that acts as both an input client and an output client.  The server is capable of both receiving and sending data.  The server contains a number of channels.  Input clients send information to these channels, and output clients subscribe to these channels.

 

Creating a Server

 

The following example of a C++ server is only one implementation.  See the example code available in the ProctoLogic kit to see examples of  a more advanced C++ server that uses threads and semaphores to ensure that all clients get equal network time.

 

The C++ Server

 

The creation of a server is very similar to the creation of an output client, so check out Chapter 2, “Clients” to see what a C++ output client looks like.

 
 
You will first need to include files to deal with sockets, libraries, as well as the provided ProctoLogic files that will deal mostly with the namespace and the sending and receiving of Open SoundControl messages.
 
#include <getopt.h>
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
#include <vector>
 
#include "MapServiceManager.h"
#include "OSCAssociativeNamespace.h"
#include "OSCCallable.h"
#include "OSCDocumenter.h"
#include "OSCLister.h"
#include "OSCPrintCallable.h"
#include "OSCProcessor.h"
#include "OSCReplyCallable.h"
#include "OSCTypeReturner.h"
#include "standards.h"
#include "Subscriber.h"
#include "Broadcaster.h"
#include "InputStream.h"
#include "UDPService.h"
 
You will need to create some objects to perform a variety of tasks.  
 
bool success;
  int  portNumber;
 
  // Network services framework:
  MapServiceManager *m;
  UDPService        *listener;
  
  // Standard components of the server:
  OSCDocumentor     *documentman;
  OSCLister         *lister;
  OSCNamespace      *name;
  OSCProcessor      *processor;
  OSCTypeReturner   *typeReturner;
  
  // Our custom callback:
  OSCCallable       *callback;
 
  // For the communications channel:
  Subscriber  *subscriber;
  Broadcaster *broadcaster;
  InputStream *instream; 
  
  m         = new MapServiceManager();        // Keeps track of potentially 
                                              // many listening sockets,
                                              // using select()
  name      = new OSCAssociativeNamespace();  // Maps callback names to objects
  processor = new OSCProcessor();             // Dispatches incoming messages
  listener  = new UDPService();               // Recieves data from UDP
 
  processor -> setNamespace(name);            // Looks for callbacks in
                                              // this namespace
  listener  -> setProcessor(processor);       // Sends recieved UDP messages
                                              // to processor
 
Now, you need to set the server to listen on a specified port for UDP messages.  Observe the error checking.
 
portNumber  = PORT;                         // PORT defined in standards.h
  success     = listener->init(portNumber);   // Create a listening UDP socket
 
  if(!success)
  {
    cerr << "Couldn't initialize the UDP listener." << endl;
    exit(1);
  }
  
  m->addService(listener);
  
This is where you add features to your server.  These will allow users to get a list of everything in the namespace, as well as request documentation on the objects in the namespace.  In addition to documentation, type tags will also be requestable.  Notice how documentation must be set on each object, though.
  
  lister = new OSCLister();       // Handles listing queries
  lister -> setNamespace(name);   // Returns addresses from this namespace
  
  name -> add("/list", lister);   // Now, it's available as '/list'.
 
  
  documentman = new OSCDocumentor();    // Handles documentation requests
  documentman -> setNamespace(name);    // Documents objects in this namespace
  documentman -> setDocumentation("Take a string (an OSC address) as an argument and return documentation for that address.");
                                        // It is itself documented.
 
  name -> add("/document", documentman);
 
  
  typeReturner = new OSCTypeReturner(); // Handles type signature queries
  typeReturner -> setNamespace(name);
  typeReturner -> setDocumentation("Take the OSC address of a callback and return its address and type signature.");
  
  name -> add("/typeSignature", typeReturner);
 
 
Like an output client, you can set up your server to have callable objects, also.  
 
  // After we have set up our default namespace, we add our own callbacks.
 
  callback = new OSCPrintCallable("Hello.");
  callback -> setTypeSignature("s");  // This callback expects a single string.
                                      // Omit the magic comma here.
                                      // This callback doesn't really care
                                      // about the types of its arguments; it
                                      // just prints its data out verbatim.
 
  callback -> setDocumentation("Print a message on the server when called.");
 
  name->add("/print", callback);      // Now we can send messages to '/print'
                                      // on this server, and this callback's
                                      // call(string, Transmit*) method is
                                      // called with the OSC message we sent
                                      // and an object that may be used to
                                      // reply.
 
Since this is a server, it must have channels.  Input clients will be providing these channels with data.  All output clients that have subscribed to these channels using the Subscriber object will receive the data when it is broadcast.  Notice how an address that will result in a subscription is added to the namespace in addition to the address which will actually be giving the data to those who are subscribed.  
 
  // Build a channel of communication on the server.
  // This channel can be subscribed to by sending a message
  // to /subscribe/example and it can be given data by 
  // sending a message to /in/example
  
  subscriber  = new Subscriber();     // Adds clients to the broadcaster's list
  broadcaster = new Broadcaster();    // Sends messages to a list of clients
  instream    = new InputStream();    // Accepts data and gives to broadcaster
 
  instream->setDocumentation("Forward messages to subscribing parties");
  subscriber->setDocumentation("Message with bound socket to subscribe");
 
  subscriber->setBroadcaster(broadcaster);  // subscriber updates
                                            // broadcaster's subscription list
  instream->setTransmit(broadcaster);       // instream takes OSC data and
                                            // passes it verbatim (including
                                            // the address) to broadcaster,
                                            // its Transmit object.
 
  // subscriber and instream live in the OSC namespace,
  // here with the addresses /subscriber/example and
  // /in/example.
  name->add("/subscribe/example", subscriber);
  name->add("/in/example", instream);
 
 
Finally, set up the server to continuously accept UDP packets.
 
m->serveForever();                  // Wait for UDP packets on the 
                                      // default port.  Does not return.
 
 

Subscribing to an Existing Server

 

Once you have created your server, it is time to let output clients subscribe to it.  This act will add an output client to the subscription list for a specific channel.  Whenever data is sent to the channel, the server will broadcast that data to everyone subscribed.  When a subscription request is received, the server will add the requesting output client to the subscription list along with an expiration time.  The expiration time set to a variable length of time in the future.  The default time in the ProctoLogic kit for expiration times is 5 minutes.  Once the expiration time has passed, the subscription will be cancelled, and the output client will no longer receive data on that communication channel.  For this reason, you will have to set up your clients to resubscribe often enough to keep the subscription valid.

 

To have an output client subscribe to a channel on a server, you simply need to send an Open SoundControl message to the appropriate channel on the server.  Since this subscription will expire after a set amount of time, it is important to make sure that you write your client to resubscribe.  In the server above, we added a channel that would deal with subscription requests.  That code is pictured below.

 

  // Build a channel of communication on the server.
  // This channel can be subscribed to by sending a message
  // to /subscribe/example and it can be given data by 
  // sending a message to /in/example
  
  subscriber  = new Subscriber();     // Adds clients to the broadcaster's list
  broadcaster = new Broadcaster();    // Sends messages to a list of clients
  instream    = new InputStream();    // Accepts data and gives to broadcaster
 
  instream->setDocumentation("Forward messages to subscribing parties");
  subscriber->setDocumentation("Message with bound socket to subscribe");
 
  subscriber->setBroadcaster(broadcaster);  // subscriber updates
                                            // broadcaster's subscription list
  instream->setTransmit(broadcaster);       // instream takes OSC data and
                                            // passes it verbatim (including
                                            // the address) to broadcaster,
                                            // its Transmit object.
 
  // subscriber and instream live in the OSC namespace,
  // here with the addresses /subscriber/example and
  // /in/example.

  name->add("/subscribe/example", subscriber);

 

The channel set up to deal with subscriptions is “/subscribe/example”.  Therefore, an output client that wished to subscribe to this channel would need to send a message to that channel, and the subscriber will make sure that the IP address and port number of the machine will be added to the subscription list.  See Chapter 2, “Clients” in the “Input Clients” or refer to the example code included in the ProctoLogic kit to see how this message can be constructed.

 4

 
       Utilities

 

 

This chapter will discuss a variety of utilities that we have included for you.  They will provide an easy way to get started, as well as an easy way to test your own code.  Mainly, these utilities will provide easy ways to send or receive messages.

 

sendOSC.py

sendOSC is a simple program for sending OSC messages to a listening OSC program.  It takes its arguments and turns them into the most likely OSC types (between int, float and string).  The first argument is the address of the remote machine.  There is not currently an option for setting the port number of the remote machine.

 

getOSC.py

getOSC is a program that listens for OSC messages on port 4950.  It’s useful for testing programs like sendOSC; just run it and watch it print hex dumps of the data it receives.

 

replyOSC.py

replyOSC is a combination of sendOSC and getOSC; it works exactly like sendOSC except that it listens for one response from the remote host.  It’s useful for doing quick listings or documentation requests against the C++ OSC programs.