kzra
Published © MIT

Guitar Pickguard Wireless MIDI Controller

Add a wireless MIDI controller to your guitar's pickguard that detects note presses via capacitive touch sensing.

IntermediateWork in progress643
Guitar Pickguard Wireless MIDI Controller

Things used in this project

Hardware components

Rotary potentiometer (generic)
Rotary potentiometer (generic)
×2
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
×3
Feather 32u4 Bluefruit LE
Adafruit Feather 32u4 Bluefruit LE
×1
Capacitive Touch Sensor Breakout - MPR121
Adafruit Capacitive Touch Sensor Breakout - MPR121
×1
Toggle Switch, SPDT
Toggle Switch, SPDT
×1
SparkFun Snappable Protoboard
SparkFun Snappable Protoboard
×1

Hand tools and fabrication machines

Tape, PVC (Polyvinyl Chloride)
Tape, PVC (Polyvinyl Chloride)
Tape, Hook and Loop
Tape, Hook and Loop
Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

Circuit schematic

Code

Arduino-MIDI-Scratchplate-Controller

C/C++
You will also need to download the Bluefruit LE Config.h file. See here for instructions: https://learn.adafruit.com/bluefruit-le-connect/library-and-config
/* Scratchplate Midi Controller
 * Version 1.0.0 - ALPHA
 *  
 * This midi controller should work with any MIDI compatible synth. 
 * I have been using it with Audio Synth One on iOS with good results.
 * 
 * The capacitive sensor allows 12 chromatic notes to be played.
 * 
 * Octave can be changed via two push buttons. The default range is 4 octaves but this can easily be changed.
 * 
 * A third push button allows you to change the device from normal mode to sustain mode. In sustain mode only one note can be played at a time. 
 * This is equivalent to setting 'MONO' and 'HOLD' on a synth. 
 * 
 * Finally two potentiometers control MIDI CC 1 and MIDI CC 7. 
 * These are traditionally mod wheel and volume knobs, but through MIDI learn can usually be set to anything you like. 
 * 
 * For full instructions please see: https://create.arduino.cc/projecthub/projects/238dd5
 * 
 * Acknowledgements: This sketch is adapted from ble_neopixel_mpr121.ino, by Todd Treece, copyright of Adafruit
 
 (C) MIT Kzra 2021
 */

// libraries
#include "Wire.h"
#include "Adafruit_MPR121.h"
#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_SPI.h"
#include "Adafruit_BLEMIDI.h"
#include "BluefruitConfig.h"

// defined constants
#define FACTORYRESET_ENABLE         1
#define MINIMUM_FIRMWARE_VERSION    "0.7.0"
#define IRQ_PIN  A4
#define MOD_POT A0
#define VOL_POT A1
#define OCTAVE_UP 13
#define OCTAVE_DOWN 12
#define SUSTAIN 11

//set up capacitive sensor and bluetooth LE
Adafruit_MPR121 cap = Adafruit_MPR121();
Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);
Adafruit_BLEMIDI blemidi(ble);

// prime dynamic values
int channel = 0;
int octave = 4;
int potVal;
int modVal;
int lastModVal;
int volVal;
int lastVolVal;
bool sustainMode = false;
int prevNote; 
uint16_t lasttouched = 0;
uint16_t currtouched = 0;
bool isConnected = false;

// constant values 
const int pitch[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
const int PADS = 12;
const int vel = 127;

// A small helper
void error(const __FlashStringHelper*err) {
  Serial.println(err);
  while (1);
}
void connected(void)
{
  isConnected = true;
  Serial.println(F(" CONNECTED!"));
}

void disconnected(void)
{
  Serial.println("disconnected");
  isConnected = false;
}


void setup() {
   Serial.print(F("Initialising the Bluefruit LE module: "));

  if ( !ble.begin(VERBOSE_MODE) )
  {
    error(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
  }
  Serial.println( F("OK!") );

  if ( FACTORYRESET_ENABLE )
  {
    /* Perform a factory reset to make sure everything is in a known state */
    Serial.println(F("Performing a factory reset: "));
    if ( ! ble.factoryReset() ) {
      error(F("Couldn't factory reset"));
    }
  }

  //ble.sendCommandCheckOK(F("AT+uartflow=off"));
  ble.echo(false);

  Serial.println("Requesting Bluefruit info:");
  /* Print Bluefruit information */
  ble.info();
  
  /* Set BLE callbacks */
  ble.setConnectCallback(connected);
  ble.setDisconnectCallback(disconnected);
  
  Serial.println(F("Enable MIDI: "));
  if ( ! blemidi.begin(true) )
  {
    error(F("Could not enable MIDI"));
  }
    
  ble.verbose(false);
  Serial.print(F("Waiting for a connection..."));
  
  // set mpr121 IRQ pin to input
  pinMode(IRQ_PIN, INPUT);

  // bail if the mpr121 init fails
  if (! cap.begin(0x5A))
    while (1);

}

void loop() {
  // check for sustain 
  if(digitalRead(SUSTAIN) == HIGH){
    if(sustainMode == false){
      sustainMode = true;
    }else {
      sustainMode = false;
      }
    // send a note off event on every pitch in current octave range  
    for (uint8_t i=0; i < PADS; i++) {
       midi(channel, 0x8, pitch[i] + (12 * octave), 0x0);
    }
    Serial.println(sustainMode);
    delay(500);
  }
  // if mpr121 irq goes low, there have been
  // changes to the button states, so read the values
  if(digitalRead(IRQ_PIN) == LOW){
    // read current values
    currtouched = cap.touched();
    // normal mode
    if(sustainMode == false){
      cap_midi_normal();
    // sustain mode
    } else {
      cap_midi_sustain();
    }
    lasttouched = currtouched;
  }
  if(digitalRead(OCTAVE_UP) == HIGH){
    // send a note off event on every pitch in prev octave range  
    for (uint8_t i=0; i < PADS; i++) {
          midi(channel, 0x8, pitch[i] + (12 * octave), 0x0);
    }
    octave++;
    if(octave == 7){
      octave = 3;
    }
    Serial.println(octave);
    delay(500);
  }
  if(digitalRead(OCTAVE_DOWN) == HIGH){
    // send a note off event on every pitch in prev octave range
    for (uint8_t i=0; i < PADS; i++) {
          midi(channel, 0x8, pitch[i] + (12 * octave), 0x0);
    }
    octave--;
    if(octave == 2){
      octave = 6;
    }
    Serial.println(octave);
    delay(500);
  }
  //  read the volume pot 
  potVal = analogRead(VOL_POT);
  volVal= map(potVal, 0, 1023, 0, 127);
  if (lastVolVal != volVal) {
    Serial.print("volWheel = ");
    Serial.println(volVal);
    // It's a nibble for a CC change
    midi(channel, 0xB, 7, volVal);
    lastVolVal = volVal;
  }
  // finally read the modulation pot
  potVal = analogRead(MOD_POT);
  modVal = map(potVal, 0, 1023, 0, 127);
  //send new mod value if it has changed
  if (lastModVal != modVal) {
    Serial.print("modWheel = ");
    Serial.println(modVal);
    // It's a nibble for a CC change
    midi(channel, 0xB, 1, modVal);
    lastModVal = modVal;
  }
  
}

/* standard mode 
 * (i) notes on only while touched
 * (ii) can play multiple notes at once 
 */
void cap_midi_normal(){
    for (uint8_t i=0; i < PADS; i++) {
    // note on check
    if ((currtouched & _BV(i)) && !(lasttouched & _BV(i))) {
      // play pressed note
      midi(channel, 0x9, pitch[i] + (12 * octave), vel);
    }
    // note off check
    if ( !(currtouched & _BV(i)) && (lasttouched & _BV(i)) ) {
      // play note off
      midi(channel, 0x8, pitch[i] + (12 * octave), 0x0);
    }
   } 
}

/* in sustain mode you can only play one note at a time, and it will stay on until you 
 * (i) press the note again, or (ii) press another note 
 */
void cap_midi_sustain(){
    for (uint8_t i=0; i < PADS; i++) {
    // note on check
    if ((currtouched & _BV(i))) {
      // play pressed note
      midi(channel, 0x9, pitch[i] + (12 * octave), vel);
      // turn off last pressed note 
      midi(channel, 0x9, prevNote + (12 * octave), 0x0);
      // reset the index
      prevNote = i;
      // delay to allow user to remove hand
      delay(500);
    } 
   }
} 

// the callback that will be called by the sequencer when it needs
// to send midi commands over BLE.
void midi(byte channel, byte command, byte arg1, byte arg2) {
  // init combined byte
  byte combined = command;
  // shift if necessary and add MIDI channel
  if(combined < 128) {
    combined <<= 4;
    combined |= channel;
  }
  blemidi.send(combined, arg1, arg2);
}

Credits

kzra
0 projects • 8 followers
Contact

Comments

Please log in or sign up to comment.