DIY control pedal on Linux

I use Emacs a lot, thus pressing and releasing Ctrl all the time is really a problem for me. Recently I even started experiencing pain in finger joints due to inefficient hand position. Remapping CapsLock as additional Ctrl didn't help much.

So I embarked upon a quest to use foot pedal as additional Ctrl.

My first attempt was to buy already assembled and working USB pedal, like this one. Long story short, I waited two months, it never arrived (I got a refund, though), so I had to work with whatever I had available.

I am an amateur piano player, so I have Yamaha electronic piano with electronic suspension pedal at home. It looks like this:


It's some generic Yamaha pedal, but I think that something like Yamaha FC5 model will work just as well.

As you can see, it has TS 6.3 mm mono jack as it's output. But audio input on most sound cards is stereo and uses 3.5 mm jacks. Thus I had to find an adapter. I settled on using Proel AT100 adapter:


It still doesn't solve problem with converting mono to stereo (notice that there is only one black stripe on the output end). But it turns out that we can simply plug mono jack into stereo input - one of the channels will simply short itself to the ground, while other will work just fine (for more details, consider this question).

So, I have successfully connected my pedal to audio input. How can I now use it?

First, I needed to verify that we indeed have a signal. I used Xoscope for that, which is a wonderful tool that literally works like an oscilloscope for your audio input. There was a slight hiccup on installation - you need to reload pulseaudio (pulseaudio -k) to get it to work, but otherwise it works great.

Here's the signal waveform for pedal press:


And this happens on pedal release:


Clearly, the signal is too strong for our input and goes off-scale, making it almost impossible to reliably detect pedal press/release events. I spent several hours trying to devise a smart heuristic, but it always failed to work on some weird sequence of predal presses - and I wanted the process to be very reliable.

But not all hope was lost. Remember the second channel, the one that is supposed to be shorted to the ground? Turns out, the signal on the main channel was strong enough to "leak" into it, although with much smaller amplitude, which was exactly what I needed:


Here you can see two nice and clean events - first on pedal press, second on pedal release. No filtering or processing is now required - I can simply sample the channel several hundred times per second and change Ctrl state if signal level is beyond positive or negative threshold.

The code to capture the signal was mercilessly extracted from Xoscope source, keyboard manipulation was done via xdotool library. The best part - my "piano pedal driver" runs in userspace, no kernel modules required!

Here's the code:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <esd.h>
#include <fcntl.h>
#include <xdo.h>

#define THRESHOLD 3

int main() {
  int esd_socket = 0;

  esd_format_t format = ESD_BITS8|ESD_STEREO|ESD_STREAM|ESD_MONITOR;
  while (esd_socket == 0) {
    if ((esd_socket = esd_record_stream_fallback(format, 1000, NULL, "ppd")) < 0) {
      printf("failed to open ESD socket\n");
      return 1;
    }
    if (esd_socket == 0) {
      printf("ESD not ready: got socket #0\n");
      close(esd_socket);
      sleep(3);
    }
  }

  printf("opened ESD socket #%d\n", esd_socket);

  if (fcntl(esd_socket, F_SETFL, O_NONBLOCK) == -1) {
    printf("failed to set ESD socket to non-blocking mode\n");
    return 1;
  }

  printf("set ESD socket to non-blocking mode\n");

  xdo_t *xdo = xdo_new(NULL);

  int ctrl_on = 0;

  while (1) {
    usleep(10000); /* sleep for 10 ms */

    unsigned char buffer[1024*64];
    /* read as much audio data as we can */
    int read_bytes = read(esd_socket, buffer, sizeof(buffer));
    /* data format is simple - channels are interleaved
       first byte is left channel, second byte is right channel, and so on */
       since we are interested in right channel, we start at index 1 */
    for (int i = 1; i < read_bytes; i += 2) {
      if (buffer[i] - 128 > THRESHOLD && !ctrl_on) {
        printf("on\n");
        ctrl_on = 1;
        /* 37 is keycode for left control on my machine
           you can check it using xmodmap -pke on your machine */
        xdo_keysequence_down(xdo, CURRENTWINDOW, "37", 0);
      } else if (buffer[i] - 128 < - THRESHOLD && ctrl_on) {
        printf("off\n");
        ctrl_on = 0;
        xdo_keysequence_up(xdo, CURRENTWINDOW, "37", 0);
      }
    }
  }

  close(esd_socket);

  return 0;
}
You can get complete compilable project from github: https://github.com/Rogach/piano-pedal-driver. Make sure that you have libesd0, libaudiofile and libxdo development headers installed.

I've been using this pedal for a month already, and it is great - after several days, using it is completely effortless, and it feels like your hands are completely free. Basically, you get to feel like you are using Vim while still keeping your Emacs powers :) And my joint paint is almost completely gone.

Have fun and remember: there is always more than one solution to the problem!

Comments

Popular posts from this blog

How to create your own simple 3D render engine in pure Java

Solving quadruple dependency injection problem in Angular

Better CLI option parsing in Scala