teaching machines

Networking with Pure Data

July 10, 2019 by . Filed under electronics, music, public.

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: