Modify "Touch_MP3.ino" to allow "banks" of sounds on the Bare Conductive Board

Hello!

I'm a Design Teacher, but an Arduino coding novice. I've done a few basic Arduino projects (LEDs, Neopixels, buttons, etc), but I get a little lost beyond the basics. I have a "Bare Conductive Board" (basically a Leonardo board, with on-board support for their conductive pads).

One of the sample sketches is "Touch_MP3.ino", which plays an MP3 when a pad is triggered. Unless I misunderstand, it plays the MP3 with the same integer label as the pad. What I would like to do is modify the code so I could select a "bank" of sounds by hitting a button to switch the banks. Still only 12 at a time, since there are only 12 conductive pads on the board, but load a different set of sounds from the SD card.

*******************************************************************************

 Bare Conductive Touch MP3 player
 ------------------------------

 Touch_MP3.ino - touch triggered MP3 playback

 You need twelve MP3 files named TRACK000.mp3 to TRACK011.mp3 in the root of the
 microSD card.

 When you touch electrode E0, TRACK000.mp3 will play. When you touch electrode
 E1, TRACK001.mp3 will play, and so on.

  SD card
  │
    TRACK000.mp3
    TRACK001.mp3
    TRACK002.mp3
    TRACK003.mp3
    TRACK004.mp3
    TRACK005.mp3
    TRACK006.mp3
    TRACK007.mp3
    TRACK008.mp3
    TRACK009.mp3
    TRACK010.mp3
    TRACK011.mp3

 Based on code by Jim Lindblom and plenty of inspiration from the Freescale
 Semiconductor datasheets and application notes.

 Bare Conductive code written by Stefan Dzisiewski-Smith, Peter Krige, Pascal
 Loose

 This work is licensed under a MIT license https://opensource.org/licenses/MIT

 Copyright (c) 2016, Bare Conductive

*******************************************************************************/

// compiler error handling
#include "Compiler_Errors.h"

// touch includes
#include <MPR121.h>
#include <MPR121_Datastream.h>
#include <Wire.h>

// MP3 includes
#include <SPI.h>
#include <SdFat.h>
#include <FreeStack.h>
#include <SFEMP3Shield.h>

// touch constants
const uint32_t BAUD_RATE = 115200;
const uint8_t MPR121_ADDR = 0x5C;
const uint8_t MPR121_INT = 4;

// serial monitor behaviour constants
const bool WAIT_FOR_SERIAL = false;

// MPR121 datastream behaviour constants
const bool MPR121_DATASTREAM_ENABLE = false;

// MP3 variables
uint8_t result;
uint8_t lastPlayed = 0;

// MP3 constants
SFEMP3Shield MP3player;

// MP3 behaviour constants
const bool REPLAY_MODE = true;  // by default, touching an electrode repeatedly will
                                // play the track again from the start each time
                                //
                                // if you set this to false, repeatedly touching an
                                // electrode will stop the track if it is already
                                // playing, or play it from the start if it is not

// SD card instantiation
SdFat sd;

void setup() {
  Serial.begin(BAUD_RATE);
  pinMode(LED_BUILTIN, OUTPUT);

  if (WAIT_FOR_SERIAL) {
    while (!Serial);
  }

  if (!sd.begin(SD_SEL, SPI_HALF_SPEED)) {
    sd.initErrorHalt();
  }

  if (!MPR121.begin(MPR121_ADDR)) {
    Serial.println("error setting up MPR121");
    switch (MPR121.getError()) {
      case NO_ERROR:
        Serial.println("no error");
        break;
      case ADDRESS_UNKNOWN:
        Serial.println("incorrect address");
        break;
      case READBACK_FAIL:
        Serial.println("readback failure");
        break;
      case OVERCURRENT_FLAG:
        Serial.println("overcurrent on REXT pin");
        break;
      case OUT_OF_RANGE:
        Serial.println("electrode out of range");
        break;
      case NOT_INITED:
        Serial.println("not initialised");
        break;
      default:
        Serial.println("unknown error");
        break;
    }
    while (1);
  }

  MPR121.setInterruptPin(MPR121_INT);

  if (MPR121_DATASTREAM_ENABLE) {
    MPR121.restoreSavedThresholds();
    MPR121_Datastream.begin(&Serial);
  } else {
    MPR121.setTouchThreshold(40);
    MPR121.setReleaseThreshold(20);
  }

  MPR121.setFFI(FFI_10);
  MPR121.setSFI(SFI_10);
  MPR121.setGlobalCDT(CDT_4US);  // reasonable for larger capacitances
  
  digitalWrite(LED_BUILTIN, HIGH);  // switch on user LED while auto calibrating electrodes
  delay(1000);
  MPR121.autoSetElectrodes();  // autoset all electrode settings
  digitalWrite(LED_BUILTIN, LOW);

  result = MP3player.begin();
  MP3player.setVolume(10, 10);

  if (result != 0) {
    Serial.print("Error code: ");
    Serial.print(result);
    Serial.println(" when trying to start MP3 player");
  }
}

void loop() {
  MPR121.updateAll();

  // only make an action if we have one or fewer pins touched
  // ignore multiple touches
  if (MPR121.getNumTouches() <= 1) {
    for (int i=0; i < 12; i++) {  // check which electrodes were pressed
      if (MPR121.isNewTouch(i)) {
          if (!MPR121_DATASTREAM_ENABLE) {
            Serial.print("pin ");
            Serial.print(i);
            Serial.println(" was just touched");
          }

          digitalWrite(LED_BUILTIN, HIGH);

          if (i <= 11 && i >= 0) {
            if (MP3player.isPlaying()) {
              if (lastPlayed == i && !REPLAY_MODE) {
                // if we're already playing the requested track, stop it
                // (but only if we're not in REPLAY_MODE)
                MP3player.stopTrack();

                if (!MPR121_DATASTREAM_ENABLE) {
                  Serial.print("stopping track ");
                  Serial.println(i-0);
                }
              } else {
                // if we're already playing a different track (or we're in
                // REPLAY_MODE), stop and play the newly requested one
                MP3player.stopTrack();
                MP3player.playTrack(i-0);

                if (!MPR121_DATASTREAM_ENABLE) {
                  Serial.print("playing track ");
                  Serial.println(i-0);
                }

                lastPlayed = i;
              }
            } else {
              // if we're playing nothing, play the requested track
              MP3player.playTrack(i-0);

              if (!MPR121_DATASTREAM_ENABLE) {
                Serial.print("playing track ");
                Serial.println(i-0);
              }

              lastPlayed = i;
            }
          }
      } else {
        if (MPR121.isNewRelease(i)) {
          if (!MPR121_DATASTREAM_ENABLE) {
            Serial.print("pin ");
            Serial.print(i);
            Serial.println(" is no longer being touched");
          }

          digitalWrite(LED_BUILTIN, LOW);
        }
      }
    }
  }

  if (MPR121_DATASTREAM_ENABLE) {
    MPR121_Datastream.update();
  }
}

I'd like some advice on what the best approach would be, to narrow down my research/experimentation.

To begin with, I suppose I need to establish a variable for "BankNumber", switchable with a button press. I think I can handle that.Beyond that, I am considering ...

  • Using folders? Keep the file names the same, but somehow have the MP3 player switch to/look in the folder which corresponds to the BankNumber variable?
  • Identify different ranges? Something like: if bank zero, play TRACK000.mp3 - TRACK011.mp3, if bank 1 play TRACK012.mp3 - TRACK023.mp3, etc.
  • Rename the files themselves depending on the bank chosen? Or include a prefix or other identifier in the filename which identifies the bank? Then somehow associate A_Track000.mp3 to the first pad when Bank 1 is selected and B_Track000.mp3 when the second bank is selected?

Any advice you can give me on the easiest/best approach to accomplishing this (for a novice) would be much appreciated. Thanks!

These are the lines you have to update:

               MP3player.playTrack(i-0);

Dunno what the -0 means, but it can be used to change all occurences in one go.
You can have a bank variable, holding the offsets to add to i, e.g. 0,12,24... and replace i-0 by i+bank.

Or you add a local variable track to the loop, and initialize it to i+bank, and use it instead of all occurences of i-0. Then you also can count the banks in the bank variable (0,1,2...), and use i+bank*12 instead.

Changing the bank variable to the next bank is up to you, using a button or a combination of touch pads...

Thanks so much for taking the time, and for the clear explanation. Seems pretty straightforward. I will give it a go!

In case anyone else is trying to solve this problem, I managed to crack it pretty easily with the great advice provided by DrDiettrich, and by adding bits of the standard "StateChangeDetection" sample code from the IDE.

Here is my working and tested code. As is, it cycles through two sets (sound banks) of 12 MP3s, but that is easily changed to as many as your SD card can hold. I updated the comments and serial debugging feedback, so it should be pretty easy to figure out.

Take care!

/*******************************************************************************

 Bare Conductive Touch MP3 player
 ------------------------------

 Touch_MP3.ino - touch triggered MP3 playback (Modified for "Banks 

 You need twelve MP3 files named TRACK000.mp3 to TRACK011.mp3 in the root of the
 microSD card.

 When you touch electrode E0, TRACK000.mp3 will play. When you touch electrode
 E1, TRACK001.mp3 will play, and so on. 

NOTE: With this modified code, simply keep the pattern below. Tracks 12 - 23 become sound bank 1, etc.

  SD card
  │
    TRACK000.mp3
    TRACK001.mp3
    TRACK002.mp3
    TRACK003.mp3
    TRACK004.mp3
    TRACK005.mp3
    TRACK006.mp3
    TRACK007.mp3
    TRACK008.mp3
    TRACK009.mp3
    TRACK010.mp3
    TRACK011.mp3

 Based on code by Jim Lindblom and plenty of inspiration from the Freescale
 Semiconductor datasheets and application notes. M

 Bare Conductive code written by Stefan Dzisiewski-Smith, Peter Krige, Pascal
 Loose

Modified for multiple sound banks by MJP. This uses a simple pushbutton to cycle through the sets of sounds.

*******************************************************************************/

// compiler error handling
#include "Compiler_Errors.h"

// touch includes
#include <MPR121.h>
#include <MPR121_Datastream.h>
#include <Wire.h>

// MP3 includes
#include <SPI.h>
#include <SdFat.h>
#include <FreeStack.h>
#include <SFEMP3Shield.h>

// touch constants
const uint32_t BAUD_RATE = 115200;
const uint8_t MPR121_ADDR = 0x5C;
const uint8_t MPR121_INT = 4;

// serial monitor behaviour constants
const bool WAIT_FOR_SERIAL = false;

// MPR121 datastream behaviour constants
const bool MPR121_DATASTREAM_ENABLE = false;

// MP3 variables
uint8_t result;
uint8_t lastPlayed = 0;

// MP3 constants
SFEMP3Shield MP3player;

// MP3 behaviour constants
const bool REPLAY_MODE = true;  // by default, touching an electrode repeatedly will
                                // play the track again from the start each time
                                //
                                // if you set this to false, repeatedly touching an
                                // electrode will stop the track if it is already
                                // playing, or play it from the start if it is not

// SD card instantiation
SdFat sd;

// Button State / Bank Toggle
const int buttonPin  = 1;     // the pin that the pushbutton is attached to
int buttonState      = 0;     // current state of the button
int lastButtonState  = 0;     // previous state of the button
int bankNumber       = 0;     // remember current bank number

void setup() {
  Serial.begin(BAUD_RATE);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(buttonPin, INPUT);  // initialize the button pin as a input

  if (WAIT_FOR_SERIAL) {
    while (!Serial);
  }

  if (!sd.begin(SD_SEL, SPI_HALF_SPEED)) {
    sd.initErrorHalt();
  }

  if (!MPR121.begin(MPR121_ADDR)) {
    Serial.println("error setting up MPR121");
    switch (MPR121.getError()) {
      case NO_ERROR:
        Serial.println("no error");
        break;
      case ADDRESS_UNKNOWN:
        Serial.println("incorrect address");
        break;
      case READBACK_FAIL:
        Serial.println("readback failure");
        break;
      case OVERCURRENT_FLAG:
        Serial.println("overcurrent on REXT pin");
        break;
      case OUT_OF_RANGE:
        Serial.println("electrode out of range");
        break;
      case NOT_INITED:
        Serial.println("not initialised");
        break;
      default:
        Serial.println("unknown error");
        break;
    }
    while (1);
  }

  MPR121.setInterruptPin(MPR121_INT);

  if (MPR121_DATASTREAM_ENABLE) {
    MPR121.restoreSavedThresholds();
    MPR121_Datastream.begin(&Serial);
  } else {
    MPR121.setTouchThreshold(40);
    MPR121.setReleaseThreshold(20);
  }

  MPR121.setFFI(FFI_10);
  MPR121.setSFI(SFI_10);
  MPR121.setGlobalCDT(CDT_4US);  // reasonable for larger capacitances
  
  digitalWrite(LED_BUILTIN, HIGH);  // switch on user LED while auto calibrating electrodes
  delay(1000);
  MPR121.autoSetElectrodes();  // autoset all electrode settings
  digitalWrite(LED_BUILTIN, LOW);

  result = MP3player.begin();
  MP3player.setVolume(10, 10);

  if (result != 0) {
    Serial.print("Error code: ");
    Serial.print(result);
    Serial.println(" when trying to start MP3 player");
  }
}

void loop() {
  
  // read the pushbutton input pin
  buttonState = digitalRead(buttonPin);

  // check if the button is pressed or released
  // by comparing the buttonState to its previous state 
  if (buttonState != lastButtonState) {
    
    // change the state of the led when someone pressed the button
 if (buttonState == 1) { 
      bankNumber++; 
      if(bankNumber>1) bankNumber=0;  // Adjust this, depending on the number of banks you have. In this case, I have 2: 0 and 1.
      Serial.print("Switching to sound bank ");  
      Serial.println(bankNumber);      
}
    
    // remember the current state of the button
    lastButtonState = buttonState;
  }
  MPR121.updateAll();

  // only make an action if we have one or fewer pins touched
  // ignore multiple touches
  if (MPR121.getNumTouches() <= 1) {
    for (int i=0; i < 12; i++) {  // check which electrodes were pressed
      if (MPR121.isNewTouch(i)) {
          if (!MPR121_DATASTREAM_ENABLE) {
            Serial.print("Pin ");
            Serial.print(i);
            Serial.println(" was just touched");
          }

          digitalWrite(LED_BUILTIN, HIGH);

          if (i <= 11 && i >= 0) {
            if (MP3player.isPlaying()) {
              if (lastPlayed == i && !REPLAY_MODE) {
                // if we're already playing the requested track, stop it
                // (but only if we're not in REPLAY_MODE)
                MP3player.stopTrack();

                if (!MPR121_DATASTREAM_ENABLE) {
                  Serial.print("Stopping track ");
                  Serial.println(i+(bankNumber * 12)); // Modified instance of serial print MJP
                }
              } else {
                // if we're already playing a different track (or we're in
                // REPLAY_MODE), stop and play the newly requested one
                MP3player.stopTrack();
                MP3player.playTrack(i+(bankNumber * 12));  // This offsets the track number played, to play from the correct "bank" MJP 

                if (!MPR121_DATASTREAM_ENABLE) {
                  Serial.print("Playing track ");
                  Serial.println(i+(bankNumber * 12)); 
                }

                lastPlayed = i;
              }
            } else {
              // if we're playing nothing, play the requested track
              MP3player.playTrack(i+(bankNumber * 12)); // This offsets the track number played, to play from the correct "bank" MJP 

              if (!MPR121_DATASTREAM_ENABLE) {
                Serial.print("Playing track ");
                Serial.println(i+(bankNumber * 12)); 
              }

              lastPlayed = i;
            }
          }
      } else {
        if (MPR121.isNewRelease(i)) {
          if (!MPR121_DATASTREAM_ENABLE) {
            Serial.print("Pin ");
            Serial.print(i);
            Serial.println(" is no longer being touched");
          }

          digitalWrite(LED_BUILTIN, LOW);
        }
      }
    }
  }

  if (MPR121_DATASTREAM_ENABLE) {
    MPR121_Datastream.update();
  }
}