Touch sensor piano - DhrBaksteen/ArduinoOPL2 GitHub Wiki

In this project example we'll be using two 12-key touch sensor breakout boards to build a little piano for the OPL2 Audio Board or the OPL3 Duo. You can use the Adafruit MPR121 12-key capacitive touch sensor breakout board or a knockoff MPR121 breakout board.

Besides the two MPR121 breakout boards you need the following:

  • OPL2 Audio Board or OPL3 Duo
  • An Arduino Nano or Uno
  • Some hook up wires
  • A long breadboard to stick everything in

!!! Caution !!!

Some of the Chinese touch sensor breakout boards have unpopulated resistor pads on the back to select the address. Often these are marked as 'ADD'. The two pads are connected together and connected to GND to have a default configuration for address 0x5A. So in order to be able to select a different address for the sensor you must cut the trace between the two pads, otherwise you would be creating a short when you configure the board for address 0x5B by connecting the address pin to 3.3v. Optionally you can install a 10k SMD resistor which will allow you to use the default address if you leave the address pin unconnected. If you don't install this resistor then you should always connect the address pin to either 3.3v or GND to select the sensor's address.

Wiring

Connecting all the components of your piano is pretty straight forward, just follow these three steps:

  1. Start by connecting the OPL2 Audio Board as follows
OPL2 VCC GND A0 DATA LATCH SHIFT RESET
Arduino 5v GND 9 11 10 13 8
  1. First MPR121 breakout (left side of the keyboard)
MPR121 GND ADDR SDA SCL IRQ 3.3V VIN
Arduino GND GND A4 A5 Not connected 3.3v Not connected
  1. Second MPR121 breakout (right side of the keyboard)
MPR121 GND ADDR SDA SCL IRQ 3.3V VIN
Arduino GND 3.3v A4 A5 Not connected 3.3v Not connected

Code

For this project you need to install the Adafruit_MRP121 library. Click the link to follow the installation instructions. After installing Copy the folowing code to and upload it to your Arduino.

#include <Wire.h>
#include <Adafruit_MPR121.h>
#include <OPL2.h>
#include <midi_instruments.h>

OPL2 opl2;
Instrument instrument;
Adafruit_MPR121 keys1;
Adafruit_MPR121 keys2;

byte usedChannels[9] = { 255, 255, 255, 255, 255, 255, 255, 255, 255 };
unsigned long keyStates = 0;
unsigned long prevKeyStates = 0;
unsigned long bitMask = 0;

void setup() {
  opl2.begin();
  keys1.begin(0x5A);
  keys2.begin(0x5B);

  // Set piano instrument for all OPL2 channels.
  instrument = opl2.loadInstrument(INSTRUMENT_PIANO1);
  for (byte i = 0; i < opl2.getNumChannels(); i ++) {
    opl2.setInstrument(i, instrument);
  }
}


void loop() {
  keyStates = keys2.touched();
  keyStates = (keyStates << 12) + keys1.touched();

  bitMask = 1;
  for (byte i = 0; i < 24; i ++) {
    // Key pressed?
    if ((keyStates & bitMask) && !(prevKeyStates & bitMask)) {
      playNote(i);
    }

    // Key released?
    if (!(keyStates & bitMask) && (prevKeyStates & bitMask)) {
      stopNote(i);
    }

    bitMask <<= 1;
  }

  prevKeyStates = keyStates;
  delay(20);
}


void playNote(byte key) {
  byte note = NOTE_F + key;
  byte octave = 3 + (note / 12);
  note = note % 12;
  
  for (byte i = 0; i < opl2.getNumChannels(); i ++) {
    if (usedChannels[i] == 255) {
      usedChannels[i] = key;
      opl2.playNote(i, octave, note);
      break;
    }
  }
}


void stopNote(byte key) {
  for (byte i = 0; i < opl2.getNumChannels(); i ++) {
    if (usedChannels[i] == key) {
      usedChannels[i] = 255;
      opl2.setKeyOn(i, false);
      break;  
    }
  }
}

Finishing the project

After uploading the code touch some of the pins of the MPR121 breakout boards.

When you tried the example it probably sounded like you were pressing with your whole hand on a piano keyboard. Your fingers are much too thick to reliably press the tiny keys on your piano. To finish off this project you can build your own keyboard from a piece of cardboard and some paperclips or metal thumbtacks. Attach wires from the headers of the touch sensor boards to the thumbtacks and push them into the cardboard to create the piano keys.

You can take your piano to the next level by adding some buttons and modifying the code. For example you can change between different instruments when you press a button, change octave or maybe play some special effects. Use your imagination!

How does it work!?

Let's have a deeper look at the code to figure out how this example works.

  • We start by including the external files with the code for the sensors and the OPL2.
  • Then we create instances for the OPL2 library, an instrument and the two touch sensor breakout boards.
  • next we initialize an array with 9 elements that are all set to 255. Each entry in this array corresponds to a channel of the OPL2 chip. There are 9 channels and thus 9 entries. When we press a key on the piano we save the index of the key in this array, so we know which note is playing on which channel. Because the key indexes run from 0 to 23 we save a value of 255 in the array when a channel is not in use.
  • After the array you see that we initialize 3 variables to keep track of the current and previous state of the keys as well as a bit mask that we will look at later. The states are unsigned long variables because each needs to hold 24 bits to represent the key state.
  • The setup() function now initializes the OPL2 chip and the touch sensors. It also loads a piano instrument and assigns it to all 9 of the OPL2 channels.

loop

  • First we call the touched() function of the two sensors. This will give us the state of all 12 keys for one of the sensors as a 16-bit number. In this number a bit is 1 when an input is touched and 0 when it's not touched. To save the state of all keys in a single 24-bit number we shift the bits from the second sensor 12 bits to the left to make room for the bits of the first sensor and then add them together.
  • Next we initialize the bitMask. The bit maks is a number that represents a single bit in the key state. With a logical AND function (&) between the key state and the bit mask we can determine if a key is pressed or not. When the key is pressed the result of the logical AND function will be non-zero, or true. When the key is not pressed it will be zero, or false.
  • Now we enter a for loop that will examine all keys
    • First we check if the key is currently pressed, while in the previous run of the loop() function it was not pressed. If this is the case then it means we need to start playing the note that belongs to the key by calling the playNote(key) function.
    • The following if statement checks the opposite case. if the key is currently not pressed, but in the previous run of the loop() function it was pressed then it means that the key was released and that we need to stop playing the note by calling the stopNote(key) function.
    • Lastly we shift the bitMask one bit to the left (it's multiplied by 2) so in the next iteration of the for loop we will example the next key.
  • Lastly the loop() function sets the current keys state as the previous key state so that in the next run of loop() we can compare between the two runs.

playNote

  • The playNote(key) function is geven the index of the key that was pressed. Using the index it determines the note (0 for C and 11 for B) and the octave.
  • In the for loop we look for a channel that is not in use (remeber 255?).
  • When an unused channel is found it is marked as used by the key that was pressed and we start playing the note on the OPL2.
  • With break we exit the for loop as soon as we've found an unused channel and started playing the note. If all channels are in use then the note will not be played.

stopNote

  • The stopNote(key) function is given the index of the key that was released.
  • The for loop is used again to examine all of the OPL2 channels, but this time we look for the channel that is set to the value of key so we know on what OPL2 channel the note is playing.
  • Once we've found the channel we mark it as being free by setting its value to 255 and we stop playing the note on the OPL2.
  • We then break from the for loop since there is nothing more to do. If the index of the key is not found in the array then it means that the key was never played. probably because there were already 9 notes playing at the time the key was pressed, so we also don't need to stop playing any note.
⚠️ **GitHub.com Fallback** ⚠️