Networking with Pure Data
This post is part of a series of notes and exercises for a summer camp on making musical instruments with Arduino and Pure Data.
Our input so far has come from an Arduino plugged into our computer. It’s time to free things up. Bring on the internet! In this exercise, we will see how to make Pure Data listen for commands through a network connection. We will send it commands from a couple of other, non-Arduino interfaces.
There are two parties in a networked application: the server and the client. The server provides a service, and it responds to requests for that service from clients. Servers tend to run in an infinite loop since a client may connect to them at any time.
Protocol
As with serial port communication, networked communication assumes that both server and client speak a common protocol. For this exercise, let’s send commands to a server to play MIDI notes of a certain duration. Each command will have this form:
MIDI-NUMBER DURATION-IN-MILLISECONDS;
For example, to play middle C for 250 milliseconds, a client will send this command:
60 250;
If I had my druthers, we’d not use semi-colons. But Pure Data assumes commands are separated by semi-colons.
Server
The server must listen for commands and fire off MIDI messages. Servers are capable of listening on many possible channels. Each channel is identified by a integer port number. Some ports are well-known. When you access a webpage in a browser, for instance, you communicate on ports 80 or 443. Many online games communicate over established ports.
We will use port 9001 for this exercise, but any port over 1023 should be fair game—unless you have another service running on your computer that is already listening on that port.
In our patch, we use a netreceive PORT-NUMBER
object to receive commands. We unpack the commands into the MIDI number and duration and then use makenote
and noteout
to issue the MIDI messages. Our patch might look something like this:
To stop listening, we send a listen 0
message. To resume listening, we can send a listen 9001
message.
Telnet Client
We can masquerade as a client to our server using the telnet
utility. Open the Terminal application and enter the following command:
telnet localhost 9001
You can now type commands in the prescribed grammar directly to the server. Hit Enter/Return after each command. In this telnet session, we play note 60 for 500 milliseconds and note 64 for 1000 milliseconds:
> telnet localhost 9001 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 60 500; 64 1000;
To quit the session, press Control-]
and type exit
at the prompt.
Ruby Client
Telnet is useful for testing servers, but typing is laborious and error-prone. Let’s make another client using a different programming language. It doesn’t matter which one we use, because the network is a universal communication channel. Whatever language you use, whether it’s Python, Java, C# or something else, you’ll need to investigate how to create a socket, which is a name we give the object that holds a connection to a port. In this example, we’ll use Ruby.
Our Ruby script starts by including the socket
library and creating a couple of variables to identify the server:
#!/usr/bin/env ruby
require 'socket'
hostname = 'localhost'
port = 9001
When we’re ready to communicate, we open the socket, and we close it when we’re done:
#!/usr/bin/env ruby
require 'socket'
hostname = 'localhost'
port = 9001
socket = TCPSocket.open(hostname, port)
# send commands along to the server
socket.close
The magic happens while the socket is open. We keep the magic contained in this short example:
#!/usr/bin/env ruby
require 'socket'
hostname = 'localhost'
port = 9001
socket = TCPSocket.open(hostname, port)
print '> '
measure_duration = 500.0
while line = gets
midi, type = line.split.map(&:to_i)
delay_millis = measure_duration / type
socket.write "#{midi} #{delay_millis};"
sleep delay_millis / 1000.0
print '> '
end
socket.close
Let’s walk through this. Using gets
, we grab from the user a line of input of the form MIDI-NUMBER TYPE
, where TYPE
refers to the length of the note. The user enters 1 for a whole note, 2 for a half, 4 for a quarter, 8 for an eighth, and so on. We split the line on the separating space character, convert the two chunks of text to integers, and compute the duration in milliseconds using the note type. Using socket.write
, we send a message in the prescribed format. And then we do it all again.
Challenges
Networking is probably the most empowering technical concept we’ve discussed. You can make different entities talk to each other! Consider exploring some of this potential further in the following challenges:
- Sockets opens the door to conventional programming languages that can defer to Pure Data to generate music. That means you can use loops, functions, arrays, and GUIs in your musical explorations. How else can you make music besides entering raw MIDI messages?
- Our examples above assume the server and client are running on the same computer. That’s what
localhost
means. Try running the server on a computer that’s not hidden behind a firewall, and try running the client on a different machine. You could create a client on your phone! - It’s often the case that a single server handles requests from many different clients. Try creating multiple simultaneous clients that play music together.