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.
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.
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.
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.