Adam Warski

15 Aug 2012

Connecting a piano pedal to a computer

softwaremill
something different
hardware

It’s good to do something different once in a while. In my case, “different” still means things involving a computer – but far from the Java&Scala programming that I do daily.

As SoftwareMill is a fully distributed company, communication is very important. We use Skype a lot, but since a while ago we also use TeamSpeak with push-to-talk for our scrums and talking inside a team. It’s kind of an always-on call – you press a key combination, talk, and all other people in the room can hear you. (There’s also an open-source alternative – Mumble – but we found that it had worse echo cancellation.)

The concept is very nice, but has one flaw – when you talk, you need to hold the key combination with one hand. And when talking about code, you often want to edit it right away, for example. That’s why I wanted to somehow change the talk-trigger. And that’s where the (electronic) piano pedal comes to play.

Of course first thing I needed is the piano pedal itself. It cost about $13 (you can buy one here, if you are from Poland) and has a 6.3mm jack connector:

It works in a very straightforward way, when pressed it simply closes the circuit. I also got a 6.3mm jack mounting and some “crocodile” cables to make the connections easier:

Then comes the question how to connect this to a computer. As I have a MacBook, the only ports available are USB and FireWire. I didn’t really need very high transfer so I went with USB. Still, making a USB device and writing a driver for it is quite a complex task. That’s why, after some brainstorming with Tomek and Jacek, I decided to use an RS232 port. For that I needed two things: a RS232-USB converter and an RS232 cable with a female connector:

One important thing to keep in mind: make sure your converter has drivers for your OS. In my case I got a Prolific USB-RS232 converter (or at least something that has the PL-2303 chip), which has both official and open-source drivers for OSX. After installing and plugging in the usb you should see two new devices in /dev:

[~]$ ls /dev | grep usbserial
cu.usbserial
tty.usbserial

Now the most important part: connecting the cables. I’ve cut the serial cable in half, and took off the insulation:

Then you need to identify which color wire is which pin. There are 9 pins (in cables with the DB9 connector). Using a simple electric meter does the trick:

As I wanted to detect when a circuit is closed, I needed one pin which has voltage always applied, and a second one which can be checked for its state. One such pair is RTS/CTS (Request to send/clear to send). In modems it’s used to detect if a device is ready to receive data. You can set the RTS programatically, and then read CTS. The pedal, when pressed, simply closes the circuit between RTS and CTS:

If you want to play a bit with various connections, CoolTerm is a very nice application which shows the state of the various pins, enables turning them on and off, writing to the device etc.

Now the programming part. The natural language of choice here is C. (btw, gcc has great compilation times, but manually freeing memory feels a bit weird ;) ). First thing you need to do is open the device file:

#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <stdbool.h>
#include <string.h>

int main(int argc, char** argv) {
   int tty_fd = open("/dev/tty.usbserial", O_RDWR | O_NONBLOCK);

   // do stuff

   close(tty_fd);
   return EXIT_SUCCESS;
}

The RTS is by default set to 1, but just in case, here’s the code to set it, and read CTS:

bool get_cts(int fd) {
   int s;
   // gets the current port state - man ioctl for more info
   ioctl(fd, TIOCMGET, &s);
   return (s & TIOCM_CTS) != 0;
}

void set_rts(int fd, bool set) {
   int status;
   ioctl(fd, TIOCMGET, &status);
   if (set) {
      status |= TIOCM_RTS;
   } else {
      status &= ~TIOCM_RTS;
   }
   ioctl(fd, TIOCMSET, &status);
}

I had to monitor somehow the state of the CTS pin to check if the pedal is pressed or not. Turns out there’s a ioctl request TIOCMIWAIT in Linux with which you can do blocking waits until some signals change. Unfortunately, it’s not implemented in OS X, so I had to go with polling:

void wait_cts_change(int fd) {
   bool start = get_cts(fd);
   while(true) {
      bool current = get_cts(fd);
      if (start != current) {
         return;
      }

      usleep(100000); // 0.1s
   }
}

In the worst case it would miss 0.1s of me talking, which I think is bearable. And doesn’t use too much CPU.

Finally we just need to control TeamSpeak’s PushToTalk. Luckily it comes bundled with a ClientQuery plugin which exposes a Telnet interface on localhost:25639. Additionally, OS X comes bundled with libcurl so using telnet is pretty easy:

// To send data using libcurl, you provide userdata and a callback function
// which copies the data to libcurl's buffer.
size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
   size_t curl_size = nmemb * size;
   size_t userdata_len = strlen(userdata);
   size_t to_copy = (userdata_len < curl_size) ? userdata_len : curl_size;
   memcpy(ptr, userdata, to_copy);
   return to_copy;
}

void pushtotalk(bool pressed) {
   char* command = NULL;
   // That's the command that actives/deactives push-to-talk
   asprintf(&#038;command,
         "clientupdate client_input_deactivated=%d\nquit\n?,
         pressed ? 0 : 1);

   CURL* curl = curl_easy_init();
   curl_easy_setopt(curl, CURLOPT_URL, "telnet://localhost:25639");
   curl_easy_setopt(curl, CURLOPT_READDATA, command);
   curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

   curl_easy_perform(curl);

   curl_easy_cleanup(curl);
}

// Main loop:

while(true) {
   wait_cts_change(tty_fd);
   int current_cts=get_cts(tty_fd);
   pushtotalk(current_cts != 0);
}

No error handling or unit tests yet, but works ;). Don’t forget you need to compile with curl:

gcc -lcurl piano.c -o piano

For anybody who’s ever done some electronics this is probably trivial, but you’ve got to start somewhere. Next … wireless? ;)

Adam

comments powered by Disqus

Any questions?

Can’t find the answer you’re looking for?