XY Midi Foot Controller

lokki

Well-known member
This project turned out to be simpler and more fun than I thought. In a nutshell it is just an infrared frame (to get touch from a monitor) mounted on a piece of wood and connected via USB-Host to a Teensy 4. The Teensy 4 has some WS2812 LEDs and buttons connected. The Leds show the X-Y position when you move an object inside the frame (hand, foot, whatever) and the four coloured buttons switch between four modes. (the leds will switch colours accordingly) this setup allows for a set of 8 parameters (two simultaneous) to be changed by foot. The current code simply sends two different CC messages for every "mode", but this can of course be changed.

Short video demo:

Code:

C++:
// Simple test of USB Host Mouse/Keyboard
//
// This example is in the public domain
#include <MIDI.h>
#include "USBHost_t36.h"
#include <WS2812Serial.h>

#define BUTTON_1 2
#define BUTTON_2 3
#define BUTTON_3 4
#define BUTTON_4 5
#define BUTTON_5 6
#define LED_TEMPO 7

#define OFF    0x000000
#define RED    0x110000
#define GREEN  0x001102
#define BLUE   0x000011
#define YELLOW 0x110700


#define DEBUG 1

#define DEBOUNCE_TIME 120

const int numled = 97;

const int led_pin = 8;
byte drawingMemory[numled*3];         //  3 bytes per LED
DMAMEM byte displayMemory[numled*12];
WS2812Serial leds_ws(numled, displayMemory, drawingMemory, led_pin, WS2812_GRB);
const int led_hor = 54;
const int led_ver = 43;
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI1);

USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);
USBHIDParser hid1(myusb);
USBHIDParser hid2(myusb);
USBHIDParser hid3(myusb);
USBHIDParser hid4(myusb);
USBHIDParser hid5(myusb);
MouseController mouse1(myusb);



USBDriver *drivers[] = {&hub1, &hub2, &hid1, &hid2, &hid3, &hid4, &hid5};
#define CNT_DEVICES (sizeof(drivers)/sizeof(drivers[0]))
const char * driver_names[CNT_DEVICES] = {"Hub1","Hub2", "HID1" , "HID2", "HID3", "HID4", "HID5"};
bool driver_active[CNT_DEVICES] = {false, false, false, false};

// Lets also look at HID Input devices
USBHIDInput *hiddrivers[] = {&mouse1};
#define CNT_HIDDEVICES (sizeof(hiddrivers)/sizeof(hiddrivers[0]))
const char * hid_driver_names[CNT_DEVICES] = {"Mouse1"};
bool hid_driver_active[CNT_DEVICES] = {false, false};



bool mouse_active = 0;
bool mouse_old = 0;
int old_y[5] = {0,0,0,0,0};
int old_x[5] = {0,0,0,0,0};
int led_old_y[5] = {0,0,0,0,0};
int led_old_x[5] = {53,53,53,53,53};
elapsedMillis timer = 0;
elapsedMillis debug;
unsigned int debug_count = 0;


//buttons
int button_Pin[5] {BUTTON_1,BUTTON_2,BUTTON_3,BUTTON_4,BUTTON_5};
bool button_Status[5] {0,0,0,0,0};
bool button_Old[5] {1,1,1,1,1};
elapsedMillis button_Debounce[5] {0,0,0,0,0};
bool button_Toggle[5] = {0,0,0,0,0};
uint8_t fx_nr = 0;

//tap stuff
int tap_count = 0;
elapsedMillis tap_time = 0;
elapsedMillis tapresettime = 3000;
elapsedMillis led_tempo_timer = 0;
unsigned int led_tempo = 1000;
bool tap_lit = 0;

unsigned char S_array[] = {                         // MIDI SYSEX or command array data
  0xF0, 0x00, 0x20, 0x32, 0x32, //prefix
  0x2F, 0x66, 0x78, 0x2F, //fx
  0x30, //fx number (1 to 4)
  0x2F, 0x70, 0x61, 0x72, 0x2F, //par
  0x30, 0x31, //parameter number 0x31 for some delays 0x32 for others apparently...
  0x20,
  0x33, 0x30, 0x30, 0x30, //(delay time 3 0 0 0 (s ts hs ms))
  0xF7 //suffix
};
//
const int     S_size = sizeof(S_array) / sizeof(char);  // MIDI SYSEX or command array size  

void setup()
{
  while (!Serial && millis() < 1200) ; // wait for Arduino Serial Monitor
 
   MIDI1.begin(MIDI_CHANNEL_OMNI);
  myusb.begin();

 
  pinMode(13,INPUT_DISABLE);
  pinMode(button_Pin[0],INPUT_PULLUP);
  pinMode(button_Pin[1],INPUT_PULLUP);
  pinMode(button_Pin[2],INPUT_PULLUP);
  pinMode(button_Pin[3],INPUT_PULLUP);
  pinMode(button_Pin[4],INPUT_PULLUP);
  pinMode(LED_TEMPO,OUTPUT);
  leds_ws.begin();

    for (uint8_t i = 0; i < CNT_DEVICES; i++) {
    if (*drivers[i] != driver_active[i]) {
      if (driver_active[i]) {
        Serial.printf("*** Device %s - disconnected ***\n", driver_names[i]);
        driver_active[i] = false;
      } else {
        Serial.printf("*** Device %s %x:%x - connected ***\n", driver_names[i], drivers[i]->idVendor(), drivers[i]->idProduct());
        driver_active[i] = true;

        const uint8_t *psz = drivers[i]->manufacturer();
        if (psz && *psz) Serial.printf("  manufacturer: %s\n", psz);
        psz = drivers[i]->product();
        if (psz && *psz) Serial.printf("  product: %s\n", psz);
        psz = drivers[i]->serialNumber();
        if (psz && *psz) Serial.printf("  Serial: %s\n", psz);
      }
    }
  }

  for (uint8_t i = 0; i < CNT_HIDDEVICES; i++) {
    if (*hiddrivers[i] != hid_driver_active[i]) {
      if (hid_driver_active[i]) {
        Serial.printf("*** HID Device %s - disconnected ***\n", hid_driver_names[i]);
        hid_driver_active[i] = false;
      } else {
        Serial.printf("*** HID Device %s %x:%x - connected ***\n", hid_driver_names[i], hiddrivers[i]->idVendor(), hiddrivers[i]->idProduct());
        hid_driver_active[i] = true;

        const uint8_t *psz = hiddrivers[i]->manufacturer();
        if (psz && *psz) Serial.printf("  manufacturer: %s\n", psz);
        psz = hiddrivers[i]->product();
        if (psz && *psz) Serial.printf("  product: %s\n", psz);
        psz = hiddrivers[i]->serialNumber();
        if (psz && *psz) Serial.printf("  Serial: %s\n", psz);
        // Note: with some keyboards there is an issue that they don't output in boot protocol mode
        // and may not work.  The above code can try to force the keyboard into boot mode, but there
        // are issues with doing this blindly with combo devices like wireless keyboard/mouse, which
        // may cause the mouse to not work.  Note: the above id is in the builtin list of
        // vendor IDs that are already forced
        #if 0  // In list in older, newer code should support the N Key rollover directly
   
        #endif
      }
    }
  }
}


void loop()
{
 /*  debug_count = debug_count + 1;
  if (debug >= 1000) {
    Serial.println(debug_count);
    debug_count = 0;
    debug = 0;
  } */
//blink tempo led
  if (led_tempo_timer < (led_tempo/2) && !tap_lit) {
   digitalWrite(LED_TEMPO,HIGH);
    tap_lit = 1;
  } else if (led_tempo_timer < led_tempo && tap_lit) {
  digitalWrite(LED_TEMPO,LOW);
    tap_lit = 0;
  } else if (led_tempo_timer >= led_tempo) {
    led_tempo_timer = 0;
  }
  //read buttons
  for (uint8_t i=0;i<5;i++) {
  button_Status[i] = digitalReadFast(button_Pin[i]);
  }
//start of button1
  if (!button_Status[0] && button_Old[0]) {
    if (button_Debounce[0] > DEBOUNCE_TIME) {
      for (uint8_t i=0;i<5;i++) {
    leds_ws.setPixel(led_old_y[i],OFF);
      leds_ws.setPixel(led_old_x[i] + led_ver,OFF);
    }
leds_ws.setPixel(led_old_y[0],BLUE);
      leds_ws.setPixel(led_old_x[0] + led_ver,BLUE);
     
    leds_ws.show();
    fx_nr = 0;
   if (!button_Toggle[0]) {
    button_Toggle[0] = 1;
    //turn on/off an fx or maybe tap
    }
    else {
   //turn on/off an fx or maybe tap
      button_Toggle[0] = 0;
    }
#ifdef DEBUG
   Serial.println("button 1 ");
 #endif
 
    button_Debounce[0] = 0;
    } else button_Status[0] = 1;
  }

    if (button_Status[0] && !button_Old[0]) {
    if (button_Debounce[0] > DEBOUNCE_TIME) {
      #ifdef DEBUG
   Serial.println("button1 released");
   #endif
    //do something when released
    button_Debounce[0] = 0;
    } else button_Status[0] = 0;
  }
  button_Old[0] = button_Status[0];
  //end of button1

//start of button2
  if (!button_Status[1] && button_Old[1]) {
    if (button_Debounce[1] > DEBOUNCE_TIME) {
      for (uint8_t i=0;i<5;i++) {
    leds_ws.setPixel(led_old_y[i],OFF);
      leds_ws.setPixel(led_old_x[i] + led_ver,OFF);
    }
leds_ws.setPixel(led_old_y[1],RED);
      leds_ws.setPixel(led_old_x[1] + led_ver,RED);
     
    leds_ws.show();
    fx_nr = 1;
   if (!button_Toggle[1]) {
    button_Toggle[1] = 1;
    //turn on/off an fx or maybe tap
    }
    else {
   //turn on/off an fx or maybe tap
      button_Toggle[1] = 0;
    }
#ifdef DEBUG
   Serial.println("button 2");
 #endif
 
    button_Debounce[1] = 0;
    } else button_Status[1] = 1;
  }

    if (button_Status[1] && !button_Old[1]) {
    if (button_Debounce[1] > DEBOUNCE_TIME) {
      #ifdef DEBUG
   Serial.println("button1 released");
   #endif
    //do something when released
    button_Debounce[1] = 0;
    } else button_Status[1] = 0;
  }
  button_Old[1] = button_Status[1];
  //end of button2

//start of button3
  if (!button_Status[2] && button_Old[2]) {
    if (button_Debounce[2] > DEBOUNCE_TIME) {
      for (uint8_t i=0;i<5;i++) {
    leds_ws.setPixel(led_old_y[i],OFF);
      leds_ws.setPixel(led_old_x[i] + led_ver,OFF);
    }
leds_ws.setPixel(led_old_y[2],YELLOW);
      leds_ws.setPixel(led_old_x[2] + led_ver,YELLOW);
     
    leds_ws.show();
    fx_nr = 2;
   if (!button_Toggle[2]) {
    button_Toggle[2] = 1;
    //turn on/off an fx or maybe tap
    }
    else {
   //turn on/off an fx or maybe tap
      button_Toggle[2] = 0;
    }
#ifdef DEBUG
   Serial.println("button 3");
 #endif
 
    button_Debounce[2] = 0;
    } else button_Status[2] = 1;
  }

    if (button_Status[2] && !button_Old[2]) {
    if (button_Debounce[2] > DEBOUNCE_TIME) {
      #ifdef DEBUG
   Serial.println("button1 released");
   #endif
    //do something when released
    button_Debounce[2] = 0;
    } else button_Status[2] = 0;
  }
  button_Old[2] = button_Status[2];
  //end of button3
 
  //start of button4
  if (!button_Status[3] && button_Old[3]) {
    if (button_Debounce[3] > DEBOUNCE_TIME) {
      for (uint8_t i=0;i<5;i++) {
    leds_ws.setPixel(led_old_y[i],OFF);
      leds_ws.setPixel(led_old_x[i] + led_ver,OFF);
    }
leds_ws.setPixel(led_old_y[3],GREEN);
      leds_ws.setPixel(led_old_x[3] + led_ver,GREEN);
     
    leds_ws.show();
    fx_nr = 3;
   if (!button_Toggle[3]) {
    button_Toggle[3] = 1;
    //turn on/off an fx or maybe tap
    }
    else {
   //turn on/off an fx or maybe tap
      button_Toggle[3] = 0;
    }
#ifdef DEBUG
   Serial.println("button 4");
 #endif
 
    button_Debounce[3] = 0;
    } else button_Status[3] = 1;
  }

    if (button_Status[3] && !button_Old[3]) {
    if (button_Debounce[3] > DEBOUNCE_TIME) {
      #ifdef DEBUG
   Serial.println("button1 released");
   #endif
    //do something when released
    button_Debounce[3] = 0;
    } else button_Status[3] = 0;
  }
  button_Old[3] = button_Status[3];
  //end of button4

  //button5
      if (!button_Status[4] && button_Old[4]) {
    if (button_Debounce[4] > DEBOUNCE_TIME) {
      //send tap to element on macbook
      usbMIDI.sendControlChange(88,50,1,3);
      #ifdef DEBUG
    Serial.println("taptempo button5");
    #endif
    if (tapresettime > 3000) {
     // tapresettime = 0;
      tap_count = 0;
    }
    if (!tap_count) { tap_time = 0;
    tapresettime = 0;
    }
    tap_count = tap_count + 1;
    if (tap_count == 1) tapresettime = 0;
    if (tap_count == 2) tapresettime = 0;
    if (tap_count == 3) {
float tempo = (float)tap_time / 2.0;
      float bpm = (60000.0/tempo);
      #ifdef DEBUG
      Serial.println(tempo);
      Serial.println(bpm);
      #endif
      if (tempo < 1) tempo = 0;
      if (tempo > 3000) tempo = 3000;
      int sendtempo = (int)tempo;
      led_tempo = sendtempo;
      led_tempo_timer = 0;
      // set value of FX number in SYSEX buffer
      S_array[9] = 0x32; //fx slot 2
      S_array[16] = 0x32; // which parameter to change, depending on the delay type this varies (0x31 or 0x32)
      // set value of Ftime into SYSEX buffer
      S_array[18] = 0x30 + sendtempo / 1000;
      S_array[19] = 0x30 + (sendtempo - (sendtempo/1000) * 1000) / 100;
      S_array[20] = 0x30 + (sendtempo - (sendtempo/100) * 100) / 10;
      S_array[21] = 0x30 + (sendtempo - (sendtempo/10) * 10);
   

      MIDI1.sendSysEx(S_size,S_array);
       tapresettime = 0;
    }
    if (tap_count == 4) tapresettime = 0;
   
    if (tap_count == 5) {
      float tempo = (float)tap_time / 4.0;
      float bpm = (60000.0/tempo);
      #ifdef DEBUG
      Serial.println(tempo);
      Serial.println(bpm);
      #endif
      if (tempo < 1) tempo = 0;
      if (tempo > 3000) tempo = 3000;
      int sendtempo = (int)tempo;
      led_tempo = sendtempo;
      led_tempo_timer = 0;
      // set value of FX number in SYSEX buffer
      S_array[9] = 0x32; //fx slot 2
      S_array[16] = 0x32; // which parameter to change, depending on the delay type this varies (0x31 or 0x32)
      // set value of Ftime into SYSEX buffer
      S_array[18] = 0x30 + sendtempo / 1000;
      S_array[19] = 0x30 + (sendtempo - (sendtempo/1000) * 1000) / 100;
      S_array[20] = 0x30 + (sendtempo - (sendtempo/100) * 100) / 10;
      S_array[21] = 0x30 + (sendtempo - (sendtempo/10) * 10);
   

      MIDI1.sendSysEx(S_size,S_array);
      tap_time = 7000;
      tap_count = 0;
    }
    button_Debounce[4] = 0;
    } else button_Status[4] = 1;
  }

    if (button_Status[4] && !button_Old[4]) {
    if (button_Debounce[4] > DEBOUNCE_TIME) {
      #ifdef DEBUG
    Serial.println("button7 released");
    #endif
    //do something when released
    button_Debounce[4] = 0;
    } else button_Status[4] = 0;
  }
  button_Old[4] = button_Status[4];
  //end of button5

  myusb.Task();

    if (mouse1.available()) {
      mouse_active = 1;
      if (mouse_active != mouse_old) {
        usbMIDI.sendControlChange(3,127,1);
      mouse_old = 1;
      }
    timer = 0;

   /* Serial.print("mouseX = ");
    Serial.print(mouse1.getMouseX());
    Serial.print(",  mouseY = ");
    Serial.print(mouse1.getMouseY());
    Serial.println();
    */
    int mouse_y = 32768 - mouse1.getMouseY();
    int mouse_x = 32768 - mouse1.getMouseX();
    int midi_y = (mouse_y>>8);
    int midi_x = 127 - (mouse_x>>8);
    int led_y = mouse_y / 762;
    int led_x = mouse_x / 607;
    mouse1.mouseDataClear();
    if (fx_nr == 0) {
    if (midi_y != old_y[0]) {
    usbMIDI.sendControlChange(1,midi_y,1);
    }
    old_y[0] = midi_y;

      if (midi_x != old_x[0]) {
usbMIDI.sendControlChange(2,midi_x,1);
    }
    old_x[0] = midi_x;

    if (led_y != led_old_y[0]) {
   
    leds_ws.setPixel(led_y,BLUE);
    leds_ws.setPixel(led_old_y[0],OFF);
    leds_ws.show();
    }
    led_old_y[0] = led_y;

      if (led_x != led_old_x[0]) {
         leds_ws.setPixel(led_x + led_ver,BLUE);
    leds_ws.setPixel(led_old_x[0] + led_ver,OFF);
    leds_ws.show();

    }
    led_old_x[0] = led_x;
    }
    if (fx_nr == 1) {
     if (midi_y != old_y[1]) {
    usbMIDI.sendControlChange(4,midi_y,1);
    }
    old_y[1] = midi_y;

      if (midi_x != old_x[1]) {
usbMIDI.sendControlChange(5,midi_x,1);
    }
    old_x[1] = midi_x;

    if (led_y != led_old_y[1]) {
   
    leds_ws.setPixel(led_y,RED);
    leds_ws.setPixel(led_old_y[1],OFF);
    leds_ws.show();
    }
    led_old_y[1] = led_y;

      if (led_x != led_old_x[1]) {
         leds_ws.setPixel(led_x + led_ver,RED);
    leds_ws.setPixel(led_old_x[1] + led_ver,OFF);
    leds_ws.show();

    }
    led_old_x[1] = led_x;
    }

     if (fx_nr == 2) {
     if (midi_y != old_y[2]) {
    usbMIDI.sendControlChange(4,midi_y,1);
    }
    old_y[2] = midi_y;

      if (midi_x != old_x[2]) {
usbMIDI.sendControlChange(5,midi_x,1);
    }
    old_x[2] = midi_x;

    if (led_y != led_old_y[2]) {
   
    leds_ws.setPixel(led_y,YELLOW);
    leds_ws.setPixel(led_old_y[2],OFF);
    leds_ws.show();
    }
    led_old_y[2] = led_y;

      if (led_x != led_old_x[2]) {
         leds_ws.setPixel(led_x + led_ver,YELLOW);
    leds_ws.setPixel(led_old_x[2] + led_ver,OFF);
    leds_ws.show();

    }
    led_old_x[2] = led_x;
    }

        if (fx_nr == 3) {
     if (midi_y != old_y[3]) {
    usbMIDI.sendControlChange(4,midi_y,1);
    }
    old_y[3] = midi_y;

      if (midi_x != old_x[3]) {
usbMIDI.sendControlChange(5,midi_x,1);
    }
    old_x[3] = midi_x;

    if (led_y != led_old_y[3]) {
   
    leds_ws.setPixel(led_y,GREEN);
    leds_ws.setPixel(led_old_y[3],OFF);
    leds_ws.show();
    }
    led_old_y[3] = led_y;

      if (led_x != led_old_x[3]) {
         leds_ws.setPixel(led_x + led_ver,GREEN);
    leds_ws.setPixel(led_old_x[3] + led_ver,OFF);
    leds_ws.show();

    }
    led_old_x[3] = led_x;
    }

  } else if (timer > 20) { //the infrared frame refreshes at around 20ms
    mouse_active = 0;
    if (mouse_active != mouse_old) {
    usbMIDI.sendControlChange(3,0,1);
    mouse_old = mouse_active;
    }
  }

}

some build photos:

IMG_0690.jpg
IMG_0691.jpg
IMG_0692.jpg
IMG_0689.jpg
IMG_0693.jpg
IMG_0687.jpg
 
Last edited:
Back
Top